diff --git a/.github/ISSUE_TEMPLATE/---epic.md b/.github/ISSUE_TEMPLATE/---epic.md index ec8e553d62..2a132b4026 100644 --- a/.github/ISSUE_TEMPLATE/---epic.md +++ b/.github/ISSUE_TEMPLATE/---epic.md @@ -6,24 +6,43 @@ labels: Epic, Product Note assignees: Nikhil-Nandagopal --- -## Problem statement - -Why is this needed? -What does it hope to achieve? +# Objective +Here you fill in the objective of the feature/product that you are writing about. ## Related issues - [ ] #issue1 -## Success criteria +# Success Metrics +List of all metrics you are tracking and the desired goal. +| Goal | Metric | +| ------------- | ------------- | +| e.g. Simplify user experience | Customer satisfaction score increases | +| e.g. Simplify onboarding flow | Decrease churn rate down to 30% | -How will we know the project succeeded? +# Assumptions +List any assumptions that you have about your users, technical constraints, or business goals (e.g., Most users will access this feature from tablet). -## User story +- Assumption 1 +- Assumption 2 +- Assumption 3 -How does a user use this feature? How does it relate to the problem? +# Requirements +| Requirement | User Story | Importance | Notes | +| ------------- | ------------- | ------------- | ------------- | +| e.g. Must be mobile responsive | e.g. as a user, I want to be able to access the platform via mobile phone | High, Low or Medium | Content Cell | +| e.g. The user should be able to leave a comment | e.g as a user, I want to be able to communicate with the other members on the canvas | High, Low or Medium | Content Cell | -## Details +# Out of Scope +List the things that are out of cope or might be revisited after the first release. +- Item 1 +- Item 2 +- Item 3 -What are the specifications of the implementation? -Product notes, designs etc. +# Developer Handoff Document in Figma +Link to the developer Handoff Document: + +# Questions +| Question | Answer | Date Answered | +| ------------- | ------------- | ------------- | +| e.g. How might we ensure that the comments section doesn't cover the canvas | Content Cell | Content Cell | +| Content Cell | Content Cell | Content Cell | diff --git a/.github/workflows/client-build.yml b/.github/workflows/client-build.yml index a642f67d85..b9c79b1e5c 100644 --- a/.github/workflows/client-build.yml +++ b/.github/workflows/client-build.yml @@ -103,8 +103,6 @@ jobs: with: fullCoverageDiff: false runCommand: cd app/client && REACT_APP_ENVIRONMENT=${{steps.vars.outputs.REACT_APP_ENVIRONMENT}} yarn run test:unit - # percentage of drop in coverage accepted - delta: 2 # We burn React environment & the Segment analytics key into the build itself. # This is to ensure that we don't need to configure it in each installation diff --git a/.github/workflows/external-client-test.yml b/.github/workflows/external-client-test.yml index 2b41a166fa..f83389788e 100644 --- a/.github/workflows/external-client-test.yml +++ b/.github/workflows/external-client-test.yml @@ -38,7 +38,7 @@ jobs: - name: Set up JDK 1.11 uses: actions/setup-java@v1 with: - java-version: 1.11 + java-version: "11.0.10" # Retrieve maven dependencies from cache. After a successful run, these dependencies are cached again - name: Cache maven dependencies diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml index d5a79b2dd9..3ac0df6d93 100644 --- a/.github/workflows/server.yml +++ b/.github/workflows/server.yml @@ -50,7 +50,7 @@ jobs: - name: Set up JDK 1.11 uses: actions/setup-java@v1 with: - java-version: 1.11 + java-version: "11.0.10" # Retrieve maven dependencies from cache. After a successful run, these dependencies are cached again - name: Cache maven dependencies diff --git a/README.md b/README.md index 7488dc2567..2f0080c996 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

- Try Online Sandbox + Try Online Sandbox

Documentation @@ -63,7 +63,7 @@ But if you’d rather check out some real applications that can be built with Ap The following steps introduce you to building a simple user-list dashboard on Appsmith. -1. [Sign up on Appsmith Cloud](https://bit.ly/appsmith-signup-github) or [Deploy Appsmith](https://docs.appsmith.com/setup). +1. [Sign up on Appsmith Cloud](https://app.appsmith.com/signup?utm_source=github&utm_medium=social&utm_content=website&utm_campaign=null&utm_term=website) or [Deploy Appsmith](https://docs.appsmith.com/setup). 2. Create a new app within the organization that has already been created for you. 3. Click on the `+` icon next to the `Queries` section to add a new query in the mock database 1. Name the query `usersQuery`. @@ -79,12 +79,12 @@ Connect your own data to build apps for your team. [Read more here.](https://doc ## 📚 Tutorials 1. [Building an Admin Panel on MongoDB using Appsmith](https://blog.appsmith.com/building-an-admin-panel-with-mongodb-using-appsmith) ([Video](https://www.youtube.com/watch?v=tisUaIgI86k)) -2. [Building a customer support dashboard in Appsmith](https://www.youtube.com/watch?v=-O_6OLREEzo&t=272s) -3. [Running CI/CD jobs manually using Appsmith](https://blog.appsmith.com/how-to-run-manual-jobs-in-gitlab-cicd) ([Video](https://www.youtube.com/watch?v=CYdeJcD4I8A)) -4. [Building a calendly clone in Appsmith](https://blog.appsmith.com/how-to-build-a-calendly-clone-in-30-minutes) -5. [Building Internal Tools with Appsmith](https://youtu.be/eYYYfuW-kEE) `Community` -6. [Building an Issue Tracker with Appsmith](https://dev.to/pjmantoss/how-to-build-an-issue-tracker-with-appsmith-204e) `Community` - +2. [Building a Customer Support Dashboard in Appsmith](https://www.youtube.com/watch?v=-O_6OLREEzo&t=272s) +3. [Building a Store Catalogue Management System using Appsmith and GraphQL](https://blog.appsmith.com/building-a-store-catalogue-management-system-using-appsmith-and-graphql) +4. [Running CI/CD Jobs Manually using Appsmith](https://blog.appsmith.com/how-to-run-manual-jobs-in-gitlab-cicd) ([Video](https://www.youtube.com/watch?v=CYdeJcD4I8A)) +5. [Building a Calendly Clone in Appsmith](https://blog.appsmith.com/how-to-build-a-calendly-clone-in-30-minutes) +6. [Building Internal Tools with Appsmith](https://youtu.be/eYYYfuW-kEE) `Community` +7. [Building an Issue Tracker with Appsmith](https://dev.to/pjmantoss/how-to-build-an-issue-tracker-with-appsmith-204e) `Community` ## 📕 Support & Troubleshooting diff --git a/app/client/cypress/fixtures/example.json b/app/client/cypress/fixtures/example.json index 3e446160e2..598a710a31 100644 --- a/app/client/cypress/fixtures/example.json +++ b/app/client/cypress/fixtures/example.json @@ -102,6 +102,7 @@ "AlertModalName": "Alert_Modal", "FormModalName": "Form_Modal", "TextLabelValue": "Test Text Label", + "TextLabelValueScrollable": "Test Text Label to check scroll feature", "TextName": "TestTextBox", "TextLabel": "Paragraph", "TextBody": "Heading 2", diff --git a/app/client/cypress/fixtures/resetPassword.json b/app/client/cypress/fixtures/resetPassword.json new file mode 100644 index 0000000000..f6508e3653 --- /dev/null +++ b/app/client/cypress/fixtures/resetPassword.json @@ -0,0 +1,7 @@ +{ + "responseMeta": { + "status": 200, + "success": true + }, + "data": true +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/textDsl.json b/app/client/cypress/fixtures/textDsl.json new file mode 100644 index 0000000000..d9821d17da --- /dev/null +++ b/app/client/cypress/fixtures/textDsl.json @@ -0,0 +1,45 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 966, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 240, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 16, + "minHeight": 280, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "isVisible": true, + "text": "Label", + "fontSize": "PARAGRAPH", + "fontStyle": "BOLD", + "textAlign": "LEFT", + "textColor": "#231F20", + "widgetName": "Text1", + "version": 1, + "type": "TEXT_WIDGET", + "isLoading": false, + "parentColumnSpace": 57.875, + "parentRowSpace": 40, + "leftColumn": 4, + "rightColumn": 8, + "topRow": 1, + "bottomRow": 2, + "parentId": "0", + "widgetId": "266vj9u1mr" + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_GeneralProperty_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_GeneralProperty_spec.js index f1b9c0dac1..e66f9eedfb 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_GeneralProperty_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_GeneralProperty_spec.js @@ -126,6 +126,14 @@ describe("Table Widget property pane feature validation", function() { cy.get(commonlocators.editPropCrossButton).click(); }); + it("Edit column name and test for table header changes", function() { + cy.openPropertyPane("tablewidget"); + cy.editColumn("email"); + cy.editColName("Email Address"); + cy.get(".draggable-header:contains('Email Address')").should("be.visible"); + cy.get(commonlocators.editPropCrossButton).click(); + }); + it("Test to validate text color and text background", function() { cy.openPropertyPane("tablewidget"); cy.get(widgetsPage.textColor) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_PropertyPane_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_PropertyPane_spec.js index 8329b92cda..1f2035eeab 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_PropertyPane_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_PropertyPane_spec.js @@ -10,21 +10,8 @@ describe("Table Widget property pane feature validation", function() { cy.addDsl(dsl); }); - it("Check collapse section feature in property pane", function() { - cy.openPropertyPane("tablewidget"); - //check open and collapse - cy.get(commonlocators.collapsesection) - .first() - .should("be.visible") - .click(); - cy.assertControlVisibility("tabledata"); - }); - it("Check open section and column data in property pane", function() { - cy.get(commonlocators.collapsesection) - .scrollIntoView() - .first() - .click(); + cy.openPropertyPane("tablewidget"); cy.tableColumnDataValidation("id"); cy.tableColumnDataValidation("email"); cy.tableColumnDataValidation("userName"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_new_feature_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_new_feature_spec.js new file mode 100644 index 0000000000..a35162eca6 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_new_feature_spec.js @@ -0,0 +1,109 @@ +const commonlocators = require("../../../../locators/commonlocators.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); +const publishPage = require("../../../../locators/publishWidgetspage.json"); +const dsl = require("../../../../fixtures/textDsl.json"); +const pages = require("../../../../locators/Pages.json"); + +describe("Text Widget color/font/alignment Functionality", function() { + before(() => { + cy.addDsl(dsl); + }); + + beforeEach(() => { + cy.openPropertyPane("textwidget"); + }); + + it("Text-TextStyle Heading, Text Name Validation", function() { + //changing the Text Name and verifying + cy.widgetText( + this.data.TextName, + widgetsPage.textWidget, + widgetsPage.textWidget + " " + commonlocators.widgetNameTag, + ); + + //Changing the text label + cy.testCodeMirror(this.data.TextLabelValueScrollable); + + cy.ChangeTextStyle( + this.data.TextHeading, + commonlocators.headingTextStyle, + this.data.TextLabelValueScrollable, + ); + cy.wait("@updateLayout"); + cy.PublishtheApp(); + cy.get(commonlocators.headingTextStyle) + .should("have.text", this.data.TextLabelValueScrollable) + .should("have.css", "font-size", "24px"); + cy.get(publishPage.backToEditor).click({ force: true }); + }); + + it("Test to validate text format", function() { + //Changing the Text Style's and validating + cy.get(widgetsPage.italics).click({ force: true }); + cy.readTextDataValidateCSS("font-style", "italic"); + cy.get(widgetsPage.bold).click({ force: true }); + cy.readTextDataValidateCSS("font-weight", "400"); + cy.get(widgetsPage.bold).click({ force: true }); + cy.readTextDataValidateCSS("font-weight", "700"); + cy.get(widgetsPage.italics).click({ force: true }); + cy.readTextDataValidateCSS("font-style", "normal"); + cy.closePropertyPane(); + }); + + it("Test to validate color changes in text and background", function() { + //Changing the Text Style's and validating + cy.get(widgetsPage.textColor) + .first() + .click({ force: true }); + cy.xpath(widgetsPage.greenColor).click(); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(500); + cy.wait("@updateLayout"); + cy.readTextDataValidateCSS("color", "rgb(3, 179, 101)"); + cy.get(widgetsPage.textColor) + .clear({ force: true }) + .type("purple", { force: true }); + cy.wait("@updateLayout"); + cy.readTextDataValidateCSS("color", "rgb(128, 0, 128)"); + cy.get(widgetsPage.backgroundColor) + .first() + .click({ force: true }); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(500); + cy.xpath(widgetsPage.greenColor) + .first() + .click(); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(500); + cy.wait("@updateLayout"); + cy.PublishtheApp(); + cy.get(publishPage.backToEditor).click({ force: true }); + }); + + it("Test to validate text alignment", function() { + cy.get(widgetsPage.centerAlign) + .first() + .click({ force: true }); + cy.readTextDataValidateCSS("text-align", "center"); + cy.get(widgetsPage.rightAlign) + .first() + .click({ force: true }); + cy.readTextDataValidateCSS("text-align", "right"); + cy.get(widgetsPage.leftAlign) + .first() + .click({ force: true }); + cy.readTextDataValidateCSS("text-align", "left"); + cy.closePropertyPane(); + }); + + it("Test to validate enable scroll feature", function() { + cy.get(".t--property-control-enablescroll .bp3-switch").click({ + force: true, + }); + cy.wait("@updateLayout"); + cy.get(commonlocators.headingTextStyle).trigger("mouseover", { + force: true, + }); + cy.get(commonlocators.headingTextStyle).scrollIntoView({ duration: 2000 }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Hide_Page_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Hide_Page_spec.js index 9843072c95..c1c623f588 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Hide_Page_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Hide_Page_spec.js @@ -1,10 +1,11 @@ const pages = require("../../../../locators/Pages.json"); +const publish = require("../../../../locators/publishWidgetspage.json"); const pageOne = "MyPage1"; const pageTwo = "MyPage2"; -describe("Hide page", function() { - it("Hide page", function() { +describe("Hide / Show page test functionality", function() { + it("Hide page test ", function() { cy.Createpage(pageOne); cy.Createpage(pageTwo); @@ -14,8 +15,19 @@ describe("Hide page", function() { .click({ force: true }); cy.get(pages.hidePage).click({ force: true }); cy.ClearSearch(); - cy.PublishtheApp(); cy.get(".t--page-switch-tab").should("have.length", 2); }); + + it("Show page test ", function() { + cy.get(publish.backToEditor).click(); + cy.GlobalSearchEntity(pageOne); + cy.xpath(pages.popover) + .last() + .click({ force: true }); + cy.get(pages.showPage).click({ force: true }); + cy.ClearSearch(); + cy.PublishtheApp(); + cy.get(".t--page-switch-tab").should("have.length", 3); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Page_Load_Spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Page_Load_Spec.js index a5990dec47..3b6165f04b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Page_Load_Spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Pages/Page_Load_Spec.js @@ -1,5 +1,7 @@ const dsl = require("../../../../fixtures/PageLoadDsl.json"); const commonlocators = require("../../../../locators/commonlocators.json"); +const pages = require("../../../../locators/Pages.json"); +const publish = require("../../../../locators/publishWidgetspage.json"); describe("Page Load tests", () => { before(() => { @@ -61,4 +63,27 @@ describe("Page Load tests", () => { "This is Page 1", ); }); + it("Hide Page and validate published app", () => { + cy.get(publish.backToEditor).click(); + cy.GlobalSearchEntity("Page1"); + cy.xpath(pages.popover) + .last() + .click({ force: true }); + cy.get(pages.hidePage).click({ force: true }); + cy.ClearSearch(); + cy.PublishtheApp(); + // Assert active page DSL + cy.get(commonlocators.headingTextStyle).should( + "have.text", + "This is Page 1", + ); + cy.get(publish.backToEditor).click(); + cy.SearchEntityandOpen("Page2"); + cy.PublishtheApp(); + // Assert active page DSL + cy.get(commonlocators.headingTextStyle).should( + "have.text", + "This is Page 2", + ); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/EmptyDataSource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/EmptyDataSource_spec.js new file mode 100644 index 0000000000..cf8ceae494 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/EmptyDataSource_spec.js @@ -0,0 +1,40 @@ +const queryLocators = require("../../../../locators/QueryEditor.json"); +const datasource = require("../../../../locators/DatasourcesEditor.json"); + +let datasourceName; + +describe("Create a query with a empty datasource, run, save the query", function() { + beforeEach(() => { + cy.startRoutesForDatasource(); + }); + + it("Create a empty datasource", function() { + cy.NavigateToDatasourceEditor(); + cy.get(datasource.PostgreSQL).click(); + cy.testSaveDatasource(); + cy.get("@createDatasource").then((httpResponse) => { + datasourceName = httpResponse.response.body.data.name; + }); + }); + + it("Create a query for empty/incorrect datasource and validate", () => { + cy.NavigateToQueryEditor(); + cy.contains(".t--datasource-name", datasourceName) + .find(queryLocators.createQuery) + .click(); + + cy.get(queryLocators.templateMenu).click(); + cy.get(".CodeMirror textarea") + .first() + .focus() + .type("select * from users limit 10"); + + cy.EvaluateCurrentValue("select * from users limit 10"); + cy.runQuery(); + cy.get(".react-tabs p") + .last() + .contains( + "[Missing endpoint., Missing username for authentication., Missing password for authentication.]", + ); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/UserProfile/UpdateUsersName_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/UserProfile/UpdateUsersName_spec.js index 079051246e..f609ee882d 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/UserProfile/UpdateUsersName_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/UserProfile/UpdateUsersName_spec.js @@ -5,7 +5,7 @@ describe("Update a user's name", function() { it("Update a user's name", function() { cy.get(homePage.profileMenu).click(); - cy.get(".t--edit-profile").click(); + cy.get(".t--edit-profile").click({ force: true }); cy.generateUUID().then((uid) => { username = uid; @@ -14,12 +14,37 @@ describe("Update a user's name", function() { // Waiting as the input onchange has a debounce // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(2000); - cy.get(".t--back").click(); cy.reload(); - cy.get(homePage.profileMenu).click(); cy.get(".t--user-name").contains(username); }); }); + + it("Validate email address and Reset pwd", function() { + cy.intercept("POST", "/api/v1/users/forgotPassword", { + fixture: "resetPassword.json", + }).as("resetPwd"); + cy.get(".t--edit-profile").click({ force: true }); + + // Waiting as the input onchange has a debounce + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + cy.get(".react-tabs .cs-text") + .last() + .invoke("text") + .then((text) => { + const someText = text; + expect(someText).to.equal(Cypress.env("USERNAME")); + }); + cy.get(".react-tabs a") + .last() + .contains("Reset Password") + .click(); + cy.wait("@resetPwd").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + }); }); diff --git a/app/client/cypress/locators/Pages.json b/app/client/cypress/locators/Pages.json index 19214a6437..2044baf6a8 100644 --- a/app/client/cypress/locators/Pages.json +++ b/app/client/cypress/locators/Pages.json @@ -19,5 +19,6 @@ "clonePage": ".single-select >div:contains('Clone')", "deletePage": ".single-select >div:contains('Delete')", "hidePage": ".single-select >div:contains('Hide')", - "entityQuery": ".t--entity-name:contains('Queries')" + "entityQuery": ".t--entity-name:contains('Queries')", + "showPage": ".single-select >div:contains('Show')" } \ No newline at end of file diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index fdfd69473f..958ccda954 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1409,6 +1409,14 @@ Cypress.Commands.add( }, ); +Cypress.Commands.add("readTextDataValidateCSS", (cssProperty, cssValue) => { + cy.get(commonlocators.headingTextStyle).should( + "have.css", + cssProperty, + cssValue, + ); +}); + Cypress.Commands.add("evaluateErrorMessage", (value) => { cy.get(commonlocators.evaluateMsg) .first() diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts index 238ba40ceb..cffe0e5181 100644 --- a/app/client/src/actions/datasourceActions.ts +++ b/app/client/src/actions/datasourceActions.ts @@ -26,6 +26,21 @@ export const updateDatasource = ( }; }; +export type UpdateDatasourceSuccessAction = { + type: string; + payload: Datasource; + redirect: boolean; +}; + +export const updateDatasourceSuccess = ( + payload: Datasource, + redirect = true, +): UpdateDatasourceSuccessAction => ({ + type: ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS, + payload, + redirect, +}); + export const redirectAuthorizationCode = ( pageId: string, datasourceId: string, diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index 99cc5e15b1..03d6f54900 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -97,6 +97,7 @@ export interface CopyActionRequest { export interface UpdateActionNameRequest { pageId: string; + actionId: string; layoutId: string; newName: string; oldName: string; @@ -107,11 +108,7 @@ class ActionAPI extends API { static apiUpdateCancelTokenSource: CancelTokenSource; static queryUpdateCancelTokenSource: CancelTokenSource; - static fetchAPI(id: string): AxiosPromise> { - return API.get(`${ActionAPI.url}/${id}`); - } - - static createAPI( + static createAction( apiConfig: Partial, ): AxiosPromise { return API.post(ActionAPI.url, apiConfig); @@ -135,7 +132,7 @@ class ActionAPI extends API { return API.get(ActionAPI.url, { pageId }); } - static updateAPI( + static updateAction( apiConfig: Partial, ): AxiosPromise { if (ActionAPI.apiUpdateCancelTokenSource) { @@ -158,24 +155,6 @@ class ActionAPI extends API { return API.delete(`${ActionAPI.url}/${id}`); } - static createQuery( - createQuery: CreateActionRequest, - ): AxiosPromise { - return API.post(ActionAPI.url, createQuery); - } - - static updateQuery( - updateQuery: UpdateActionRequest, - ): AxiosPromise { - if (ActionAPI.queryUpdateCancelTokenSource) { - ActionAPI.queryUpdateCancelTokenSource.cancel(); - } - ActionAPI.queryUpdateCancelTokenSource = axios.CancelToken.source(); - return API.post(ActionAPI.url, updateQuery, undefined, { - cancelToken: ActionAPI.queryUpdateCancelTokenSource.token, - }); - } - static executeAction( executeAction: ExecuteActionRequest, timeout?: number, @@ -191,12 +170,6 @@ class ActionAPI extends API { }); } - static executeQuery( - executeAction: any, - ): AxiosPromise { - return API.post(ActionAPI.url + "/execute", executeAction); - } - static toggleActionExecuteOnLoad(actionId: string, shouldExecute: boolean) { return API.put(ActionAPI.url + `/executeOnLoad/${actionId}`, undefined, { flag: shouldExecute.toString(), diff --git a/app/client/src/components/designSystems/appsmith/MapComponent.tsx b/app/client/src/components/designSystems/appsmith/MapComponent.tsx index d7083f085f..a9cde5d2d7 100644 --- a/app/client/src/components/designSystems/appsmith/MapComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/MapComponent.tsx @@ -140,29 +140,30 @@ const MyMapComponent = withGoogleMap((props: any) => { )} - {props.markers.map((marker: any, index: number) => ( - { - setMapCenter({ - ...marker, - lng: marker.long, - }); - props.selectMarker(marker.lat, marker.long, marker.title); - }} - onDragEnd={(de) => { - props.updateMarker(de.latLng.lat(), de.latLng.lng(), index); - }} - /> - ))} + {Array.isArray(props.markers) && + props.markers.map((marker: MarkerProps, index: number) => ( + { + setMapCenter({ + ...marker, + lng: marker.long, + }); + props.selectMarker(marker.lat, marker.long, marker.title); + }} + onDragEnd={(de) => { + props.updateMarker(de.latLng.lat(), de.latLng.lng(), index); + }} + /> + ))} {props.enablePickLocation && ( { it("works as expected for sort table rows", () => { @@ -29,3 +35,82 @@ describe("TableUtilities", () => { expect(sortedTableData).toStrictEqual(expected); }); }); + +describe("TransformTableDataIntoArrayOfArray", () => { + const columns: TableColumnProps[] = [ + { + Header: "Id", + accessor: "id", + minWidth: 60, + draggable: true, + metaProperties: { + isHidden: false, + type: "string", + }, + columnProperties: { + id: "id", + label: "Id", + columnType: "string", + isVisible: true, + index: 0, + width: 60, + isDerived: false, + computedValue: "", + }, + }, + ]; + it("work as expected", () => { + const data = [ + { + id: "abc", + }, + { + id: "xyz", + }, + ]; + const csvData = transformTableDataIntoCsv({ + columns, + data, + }); + const expectedCsvData = [["Id"], ["abc"], ["xyz"]]; + expect(JSON.stringify(csvData)).toStrictEqual( + JSON.stringify(expectedCsvData), + ); + }); + it("work as expected with newline", () => { + const data = [ + { + id: "abc\ntest", + }, + { + id: "xyz", + }, + ]; + const csvData = transformTableDataIntoCsv({ + columns, + data, + }); + const expectedCsvData = [["Id"], ["abc test"], ["xyz"]]; + expect(JSON.stringify(csvData)).toStrictEqual( + JSON.stringify(expectedCsvData), + ); + }); + it("work as expected with comma", () => { + const data = [ + { + id: "abc,test", + }, + { + id: "xyz", + }, + ]; + const csvData = transformTableDataIntoCsv({ + columns, + data, + }); + const expectedCsvData = [["Id"], ['"abc,test"'], ["xyz"]]; + expect(JSON.stringify(csvData)).toStrictEqual( + JSON.stringify(expectedCsvData), + ); + }); +}); diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/CommonUtilities.ts b/app/client/src/components/designSystems/appsmith/TableComponent/CommonUtilities.ts index b6396d1363..fcce5b042b 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/CommonUtilities.ts +++ b/app/client/src/components/designSystems/appsmith/TableComponent/CommonUtilities.ts @@ -1,5 +1,8 @@ -import { ColumnTypes } from "components/designSystems/appsmith/TableComponent/Constants"; -import { isPlainObject, isNil } from "lodash"; +import { + ColumnTypes, + TableColumnProps, +} from "components/designSystems/appsmith/TableComponent/Constants"; +import { isPlainObject, isNil, isString } from "lodash"; import moment from "moment"; export function sortTableFunction( @@ -51,3 +54,41 @@ export function sortTableFunction( }, ); } + +export const transformTableDataIntoCsv = (props: { + columns: TableColumnProps[]; + data: Array>; +}) => { + const csvData = []; + csvData.push( + props.columns + .map((column: TableColumnProps) => { + if (column.metaProperties && !column.metaProperties.isHidden) { + return column.Header; + } + return null; + }) + .filter((i) => !!i), + ); + for (let row = 0; row < props.data.length; row++) { + const data: { [key: string]: any } = props.data[row]; + const csvDataRow = []; + for (let colIndex = 0; colIndex < props.columns.length; colIndex++) { + const column = props.columns[colIndex]; + let value = data[column.accessor]; + if (column.metaProperties && !column.metaProperties.isHidden) { + value = + isString(value) && value.includes("\n") + ? value.replace("\n", " ") + : value; + if (isString(value) && value.includes(",")) { + csvDataRow.push(`"${value}"`); + } else { + csvDataRow.push(value); + } + } + } + csvData.push(csvDataRow); + } + return csvData; +}; diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts index 0d3b923849..1b99cdc16a 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts @@ -103,7 +103,7 @@ export interface TableColumnMetaProps { type: string; } -export interface ReactTableColumnProps { +export interface TableColumnProps { Header: string; accessor: string; width?: number; @@ -114,6 +114,8 @@ export interface ReactTableColumnProps { metaProperties?: TableColumnMetaProps; isDerived?: boolean; columnProperties: ColumnProperties; +} +export interface ReactTableColumnProps extends TableColumnProps { Cell: (props: any) => JSX.Element; } diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableDataDownload.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableDataDownload.tsx index 57ac1fdcad..06485f49ba 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableDataDownload.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableDataDownload.tsx @@ -5,7 +5,7 @@ import { ReactComponent as DownloadIcon } from "assets/icons/control/download-ta import { ReactTableColumnProps } from "components/designSystems/appsmith/TableComponent/Constants"; import { TableIconWrapper } from "components/designSystems/appsmith/TableComponent/TableStyledWrappers"; import TableActionIcon from "components/designSystems/appsmith/TableComponent/TableActionIcon"; -import { isString } from "lodash"; +import { transformTableDataIntoCsv } from "./CommonUtilities"; interface TableDataDownloadProps { data: Array>; @@ -13,63 +13,49 @@ interface TableDataDownloadProps { widgetName: string; } +const downloadDataAsCSV = (props: { + csvData: Array>; + fileName: string; +}) => { + let csvContent = ""; + props.csvData.forEach((infoArray: Array, index: number) => { + const dataString = infoArray.join(","); + csvContent += index < props.csvData.length ? dataString + "\n" : dataString; + }); + const anchor = document.createElement("a"); + const mimeType = "application/octet-stream"; + if (navigator.msSaveBlob) { + navigator.msSaveBlob( + new Blob([csvContent], { + type: mimeType, + }), + props.fileName, + ); + } else if (URL && "download" in anchor) { + anchor.href = URL.createObjectURL( + new Blob([csvContent], { + type: mimeType, + }), + ); + anchor.setAttribute("download", props.fileName); + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + } +}; + const TableDataDownload = (props: TableDataDownloadProps) => { const [selected, toggleButtonClick] = React.useState(false); const downloadTableData = () => { toggleButtonClick(true); - const csvData = []; - csvData.push( - props.columns - .map((column: ReactTableColumnProps) => { - if (column.metaProperties && !column.metaProperties.isHidden) { - return column.Header; - } - return null; - }) - .filter((i) => !!i), - ); - for (let row = 0; row < props.data.length; row++) { - const data: { [key: string]: any } = props.data[row]; - const csvDataRow = []; - for (let colIndex = 0; colIndex < props.columns.length; colIndex++) { - const column = props.columns[colIndex]; - const value = data[column.accessor]; - if (column.metaProperties && !column.metaProperties.isHidden) { - if (isString(value) && value.includes(",")) { - csvDataRow.push(`"${value}"`); - } else { - csvDataRow.push(value); - } - } - } - csvData.push(csvDataRow); - } - let csvContent = ""; - csvData.forEach(function(infoArray, index) { - const dataString = infoArray.join(","); - csvContent += index < csvData.length ? dataString + "\n" : dataString; + const csvData = transformTableDataIntoCsv({ + columns: props.columns, + data: props.data, + }); + downloadDataAsCSV({ + csvData: csvData, + fileName: `${props.widgetName}.csv`, }); - const fileName = `${props.widgetName}.csv`; - const anchor = document.createElement("a"); - const mimeType = "application/octet-stream"; - if (navigator.msSaveBlob) { - navigator.msSaveBlob( - new Blob([csvContent], { - type: mimeType, - }), - fileName, - ); - } else if (URL && "download" in anchor) { - anchor.href = URL.createObjectURL( - new Blob([csvContent], { - type: mimeType, - }), - ); - anchor.setAttribute("download", fileName); - document.body.appendChild(anchor); - anchor.click(); - document.body.removeChild(anchor); - } toggleButtonClick(false); }; diff --git a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx index 8203157e80..6851dc2745 100644 --- a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx +++ b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx @@ -239,6 +239,7 @@ class EmbeddedDatasourcePathComponent extends React.Component { ) : datasource && "id" in datasource ? ( history.push( DATA_SOURCES_EDITOR_ID_URL( diff --git a/app/client/src/components/formControls/utils.test.ts b/app/client/src/components/formControls/utils.test.ts index 4dbfe9e2a7..19b3db3230 100644 --- a/app/client/src/components/formControls/utils.test.ts +++ b/app/client/src/components/formControls/utils.test.ts @@ -244,6 +244,26 @@ describe("getConfigInitialValues test", () => { }, }, }, + { + input: [ + { + sectionName: "Settings", + children: [ + { + label: "Smart substitution", + configProperty: "datasourceConfiguration.isSmart", + controlType: "SWITCH", + initialValue: false, + }, + ], + }, + ], + output: { + datasourceConfiguration: { + isSmart: false, + }, + }, + }, ]; testCases.forEach((testCase) => { diff --git a/app/client/src/components/formControls/utils.ts b/app/client/src/components/formControls/utils.ts index 351a25768f..de74de4fcf 100644 --- a/app/client/src/components/formControls/utils.ts +++ b/app/client/src/components/formControls/utils.ts @@ -104,7 +104,7 @@ export const getConfigInitialValues = (config: Record[]) => { return parseConfig(subSection); } - if (subSection.initialValue) { + if ("initialValue" in subSection) { if (subSection.controlType === "KEYVALUE_ARRAY") { subSection.initialValue.forEach( (initialValue: string | number, index: number) => { diff --git a/app/client/src/constants/AppsmithActionConstants/formConfig/ApiDependencyConfigs.ts b/app/client/src/constants/AppsmithActionConstants/formConfig/ApiDependencyConfigs.ts new file mode 100644 index 0000000000..a80725410f --- /dev/null +++ b/app/client/src/constants/AppsmithActionConstants/formConfig/ApiDependencyConfigs.ts @@ -0,0 +1,9 @@ +export default [ + { + dependencies: { + "actionConfiguration.body": [ + "actionConfiguration.pluginSpecifiedTemplates[0].value", + ], + }, + }, +]; diff --git a/app/client/src/constants/AppsmithActionConstants/formConfig/ApiSettingsConfig.ts b/app/client/src/constants/AppsmithActionConstants/formConfig/ApiSettingsConfig.ts index 04ce69f82c..9924dc4954 100644 --- a/app/client/src/constants/AppsmithActionConstants/formConfig/ApiSettingsConfig.ts +++ b/app/client/src/constants/AppsmithActionConstants/formConfig/ApiSettingsConfig.ts @@ -28,7 +28,7 @@ export default [ controlType: "CHECKBOX", info: "Turning on this property fixes the JSON substitution of bindings in API body by adding/removing quotes intelligently and reduces developer errors", - initialValue: "false", + initialValue: false, }, // { // label: "Cache response", diff --git a/app/client/src/constants/PropertyControlConstants.tsx b/app/client/src/constants/PropertyControlConstants.tsx index f11d0174d0..abcfe209eb 100644 --- a/app/client/src/constants/PropertyControlConstants.tsx +++ b/app/client/src/constants/PropertyControlConstants.tsx @@ -1,4 +1,5 @@ import { getPropertyControlTypes } from "components/propertyControls"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; const ControlTypes = getPropertyControlTypes(); export type ControlType = typeof ControlTypes[keyof typeof ControlTypes]; @@ -42,6 +43,7 @@ export type PropertyPaneControlConfig = { hidden?: (props: any, propertyPath: string) => boolean; isBindProperty: boolean; isTriggerProperty: boolean; + validation?: VALIDATION_TYPES; useValidationMessage?: boolean; additionalAutoComplete?: ( props: any, diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts index 5139ec6dc0..39d7878cd3 100644 --- a/app/client/src/constants/WidgetValidation.ts +++ b/app/client/src/constants/WidgetValidation.ts @@ -2,34 +2,34 @@ import { WidgetProps } from "widgets/BaseWidget"; import { DataTree } from "entities/DataTree/dataTreeFactory"; import { EXECUTION_PARAM_KEY } from "constants/AppsmithActionConstants/ActionConstants"; -// Always add a validator function in ./Validators for these types -export const VALIDATION_TYPES = { - TEXT: "TEXT", - REGEX: "REGEX", - NUMBER: "NUMBER", - BOOLEAN: "BOOLEAN", - OBJECT: "OBJECT", - ARRAY: "ARRAY", - TABLE_DATA: "TABLE_DATA", - OPTIONS_DATA: "OPTIONS_DATA", - DATE_ISO_STRING: "DATE_ISO_STRING", - DEFAULT_DATE: "DEFAULT_DATE", - MIN_DATE: "MIN_DATE", - MAX_DATE: "MAX_DATE", - TABS_DATA: "TABS_DATA", - CHART_DATA: "CHART_DATA", - CUSTOM_FUSION_CHARTS_DATA: "CUSTOM_FUSION_CHARTS_DATA", - MARKERS: "MARKERS", - ACTION_SELECTOR: "ACTION_SELECTOR", - ARRAY_ACTION_SELECTOR: "ARRAY_ACTION_SELECTOR", - SELECTED_TAB: "SELECTED_TAB", - DEFAULT_OPTION_VALUE: "DEFAULT_OPTION_VALUE", - DEFAULT_SELECTED_ROW: "DEFAULT_SELECTED_ROW", - COLUMN_PROPERTIES_ARRAY: "COLUMN_PROPERTIES_ARRAY", - LAT_LONG: "LAT_LONG", - TABLE_PAGE_NO: "TABLE_PAGE_NO", - ROW_INDICES: "ROW_INDICES", -}; +// Always add a validator function in ./worker/validation for these types +export enum VALIDATION_TYPES { + TEXT = "TEXT", + REGEX = "REGEX", + NUMBER = "NUMBER", + BOOLEAN = "BOOLEAN", + OBJECT = "OBJECT", + ARRAY = "ARRAY", + TABLE_DATA = "TABLE_DATA", + OPTIONS_DATA = "OPTIONS_DATA", + DATE_ISO_STRING = "DATE_ISO_STRING", + DEFAULT_DATE = "DEFAULT_DATE", + MIN_DATE = "MIN_DATE", + MAX_DATE = "MAX_DATE", + TABS_DATA = "TABS_DATA", + CHART_DATA = "CHART_DATA", + CUSTOM_FUSION_CHARTS_DATA = "CUSTOM_FUSION_CHARTS_DATA", + MARKERS = "MARKERS", + ACTION_SELECTOR = "ACTION_SELECTOR", + ARRAY_ACTION_SELECTOR = "ARRAY_ACTION_SELECTOR", + SELECTED_TAB = "SELECTED_TAB", + DEFAULT_OPTION_VALUE = "DEFAULT_OPTION_VALUE", + DEFAULT_SELECTED_ROW = "DEFAULT_SELECTED_ROW", + COLUMN_PROPERTIES_ARRAY = "COLUMN_PROPERTIES_ARRAY", + LAT_LONG = "LAT_LONG", + TABLE_PAGE_NO = "TABLE_PAGE_NO", + ROW_INDICES = "ROW_INDICES", +} export type ValidationResponse = { isValid: boolean; @@ -38,7 +38,6 @@ export type ValidationResponse = { transformed?: any; }; -export type ValidationType = typeof VALIDATION_TYPES[keyof typeof VALIDATION_TYPES]; export type Validator = ( value: any, props: WidgetProps, diff --git a/app/client/src/entities/DataTree/dataTreeFactory.ts b/app/client/src/entities/DataTree/dataTreeFactory.ts index b78308fdef..5c6dee3737 100644 --- a/app/client/src/entities/DataTree/dataTreeFactory.ts +++ b/app/client/src/entities/DataTree/dataTreeFactory.ts @@ -12,6 +12,7 @@ import { AppDataState } from "reducers/entityReducers/appReducer"; import { DynamicPath } from "utils/DynamicBindingUtils"; import { generateDataTreeAction } from "entities/DataTree/dataTreeAction"; import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; export type ActionDescription = { type: string; @@ -52,6 +53,7 @@ export interface DataTreeAction extends Omit { export interface DataTreeWidget extends WidgetProps { bindingPaths: Record; triggerPaths: Record; + validationPaths: Record; ENTITY_TYPE: ENTITY_TYPE.WIDGET; } diff --git a/app/client/src/entities/DataTree/dataTreeWidget.test.ts b/app/client/src/entities/DataTree/dataTreeWidget.test.ts new file mode 100644 index 0000000000..23ab042a17 --- /dev/null +++ b/app/client/src/entities/DataTree/dataTreeWidget.test.ts @@ -0,0 +1,245 @@ +import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; +import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; +import { DataTreeWidget, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory"; +import { RenderModes, WidgetTypes } from "constants/WidgetConstants"; +import WidgetFactory from "utils/WidgetFactory"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; + +describe("generateDataTreeWidget", () => { + beforeEach(() => { + const getMetaProps = jest.spyOn( + WidgetFactory, + "getWidgetMetaPropertiesMap", + ); + getMetaProps.mockReturnValueOnce({ + text: undefined, + isDirty: false, + isFocused: false, + }); + + const getDerivedProps = jest.spyOn( + WidgetFactory, + "getWidgetDerivedPropertiesMap", + ); + getDerivedProps.mockReturnValueOnce({ + isValid: "{{true}}", + value: "{{this.text}}", + }); + + const getDefaultProps = jest.spyOn( + WidgetFactory, + "getWidgetDefaultPropertiesMap", + ); + getDefaultProps.mockReturnValueOnce({ + text: "defaultText", + }); + + const getPropertyConfig = jest.spyOn( + WidgetFactory, + "getWidgetPropertyPaneConfig", + ); + getPropertyConfig.mockReturnValueOnce([ + { + sectionName: "General", + children: [ + { + propertyName: "inputType", + label: "Data Type", + controlType: "DROP_DOWN", + isBindProperty: false, + isTriggerProperty: false, + }, + { + propertyName: "defaultText", + label: "Default Text", + controlType: "INPUT_TEXT", + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, + }, + { + propertyName: "placeholderText", + label: "Placeholder", + controlType: "INPUT_TEXT", + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, + }, + { + propertyName: "regex", + label: "Regex", + controlType: "INPUT_TEXT", + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.REGEX, + }, + { + propertyName: "errorMessage", + label: "Error Message", + controlType: "INPUT_TEXT", + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, + }, + { + propertyName: "isRequired", + label: "Required", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, + }, + { + propertyName: "isVisible", + label: "Visible", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, + }, + { + propertyName: "isDisabled", + label: "Disabled", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, + }, + { + propertyName: "resetOnSubmit", + label: "Reset on submit", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, + }, + ], + }, + { + sectionName: "Actions", + children: [ + { + propertyName: "onTextChanged", + label: "onTextChanged", + controlType: "ACTION_SELECTOR", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + { + propertyName: "onSubmit", + label: "onSubmit", + controlType: "ACTION_SELECTOR", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + ], + }, + ]); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("generates enhanced widget with the right properties", () => { + const widget: FlattenedWidgetProps = { + bottomRow: 0, + isLoading: false, + leftColumn: 0, + parentColumnSpace: 0, + parentRowSpace: 0, + renderMode: RenderModes.CANVAS, + rightColumn: 0, + topRow: 0, + type: WidgetTypes.INPUT_WIDGET, + version: 0, + widgetId: "123", + widgetName: "Input1", + defaultText: "Testing", + }; + + const widgetMetaProps: Record = { + text: "Tester", + isDirty: true, + }; + + const getMetaProps = jest.spyOn( + WidgetFactory, + "getWidgetMetaPropertiesMap", + ); + + getMetaProps.mockReturnValueOnce({ + text: true, + isDirty: true, + }); + + const expected: DataTreeWidget = { + bindingPaths: { + defaultText: true, + errorMessage: true, + isDirty: true, + isDisabled: true, + isFocused: true, + isRequired: true, + isValid: true, + isVisible: true, + placeholderText: true, + regex: true, + resetOnSubmit: true, + text: true, + value: true, + }, + triggerPaths: { + onSubmit: true, + onTextChanged: true, + }, + validationPaths: { + defaultText: VALIDATION_TYPES.TEXT, + errorMessage: VALIDATION_TYPES.TEXT, + isDisabled: VALIDATION_TYPES.BOOLEAN, + isRequired: VALIDATION_TYPES.BOOLEAN, + isVisible: VALIDATION_TYPES.BOOLEAN, + placeholderText: VALIDATION_TYPES.TEXT, + regex: VALIDATION_TYPES.REGEX, + resetOnSubmit: VALIDATION_TYPES.BOOLEAN, + }, + dynamicBindingPathList: [ + { + key: "isValid", + }, + { + key: "value", + }, + ], + value: "{{Input1.text}}", + isDirty: true, + isFocused: false, + isValid: "{{true}}", + text: "Tester", + bottomRow: 0, + isLoading: false, + leftColumn: 0, + parentColumnSpace: 0, + parentRowSpace: 0, + renderMode: RenderModes.CANVAS, + rightColumn: 0, + topRow: 0, + type: WidgetTypes.INPUT_WIDGET, + version: 0, + widgetId: "123", + widgetName: "Input1", + ENTITY_TYPE: ENTITY_TYPE.WIDGET, + defaultText: "Testing", + }; + + const result = generateDataTreeWidget(widget, widgetMetaProps); + + expect(result).toStrictEqual(expected); + }); +}); diff --git a/app/client/src/entities/DataTree/dataTreeWidget.ts b/app/client/src/entities/DataTree/dataTreeWidget.ts index ab53a257e6..a0dacf901f 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.ts @@ -19,7 +19,11 @@ export const generateDataTreeWidget = ( const propertyPaneConfigs = WidgetFactory.getWidgetPropertyPaneConfig( widget.type, ); - const { bindingPaths, triggerPaths } = getAllPathsFromPropertyConfig( + const { + bindingPaths, + triggerPaths, + validationPaths, + } = getAllPathsFromPropertyConfig( widget, propertyPaneConfigs, Object.fromEntries( @@ -67,6 +71,7 @@ export const generateDataTreeWidget = ( dynamicBindingPathList, bindingPaths, triggerPaths, + validationPaths, ENTITY_TYPE: ENTITY_TYPE.WIDGET, }; }; diff --git a/app/client/src/entities/Widget/utils.test.ts b/app/client/src/entities/Widget/utils.test.ts index b6d23090f3..bab8f09a93 100644 --- a/app/client/src/entities/Widget/utils.test.ts +++ b/app/client/src/entities/Widget/utils.test.ts @@ -145,6 +145,12 @@ describe("getAllPathsFromPropertyConfig", () => { onPageSizeChange: true, "primaryColumns.status.onClick": true, }, + validationPaths: { + defaultSearchText: "TEXT", + defaultSelectedRow: "DEFAULT_SELECTED_ROW", + isVisible: "BOOLEAN", + tableData: "TABLE_DATA", + }, }; const result = getAllPathsFromPropertyConfig(widget, config, { @@ -202,6 +208,14 @@ describe("getAllPathsFromPropertyConfig", () => { triggerPaths: { onDataPointClick: true, }, + validationPaths: { + "chartData[0].data": "CHART_DATA", + "chartData[0].seriesName": "TEXT", + chartName: "TEXT", + isVisible: "BOOLEAN", + xAxisName: "TEXT", + yAxisName: "TEXT", + }, }; const result = getAllPathsFromPropertyConfig(widget, config, {}); diff --git a/app/client/src/entities/Widget/utils.ts b/app/client/src/entities/Widget/utils.ts index ce23c7e529..84ae7d552a 100644 --- a/app/client/src/entities/Widget/utils.ts +++ b/app/client/src/entities/Widget/utils.ts @@ -2,6 +2,7 @@ import { WidgetProps } from "widgets/BaseWidget"; import { PropertyPaneConfig } from "constants/PropertyControlConstants"; import { get } from "lodash"; import { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; export const getAllPathsFromPropertyConfig = ( widget: WidgetProps, @@ -10,9 +11,11 @@ export const getAllPathsFromPropertyConfig = ( ): { bindingPaths: Record; triggerPaths: Record; + validationPaths: Record; } => { const bindingPaths: Record = derivedProperties; const triggerPaths: Record = {}; + const validationPaths: Record = {}; widgetConfig.forEach((config) => { if (config.children) { config.children.forEach((controlConfig: any) => { @@ -27,6 +30,10 @@ export const getAllPathsFromPropertyConfig = ( !controlConfig.isTriggerProperty ) { bindingPaths[controlConfig.propertyName] = true; + if (controlConfig.validation) { + validationPaths[controlConfig.propertyName] = + controlConfig.validation; + } } else if ( controlConfig.isBindProperty && controlConfig.isTriggerProperty @@ -66,6 +73,10 @@ export const getAllPathsFromPropertyConfig = ( !panelColumnControlConfig.isTriggerProperty ) { bindingPaths[panelPropertyPath] = true; + if (panelColumnControlConfig.validation) { + validationPaths[panelPropertyPath] = + panelColumnControlConfig.validation; + } } else if ( panelColumnControlConfig.isBindProperty && panelColumnControlConfig.isTriggerProperty @@ -97,6 +108,10 @@ export const getAllPathsFromPropertyConfig = ( !childPropertyConfig.isTriggerProperty ) { bindingPaths[childArrayPropertyPath] = true; + if (childPropertyConfig.validation) { + validationPaths[childArrayPropertyPath] = + childPropertyConfig.validation; + } } else if ( childPropertyConfig.isBindProperty && childPropertyConfig.isTriggerProperty @@ -112,7 +127,7 @@ export const getAllPathsFromPropertyConfig = ( } }); - return { bindingPaths, triggerPaths }; + return { bindingPaths, triggerPaths, validationPaths }; }; export const nextAvailableRowInContainer = ( diff --git a/app/client/src/index.css b/app/client/src/index.css index 9d0fe49e05..2ce8e9f6b7 100755 --- a/app/client/src/index.css +++ b/app/client/src/index.css @@ -102,4 +102,3 @@ div.bp3-popover-arrow { background: white !important; border-bottom: 1px solid #E7E7E7 !important; } - diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 03cf9ab4b5..b1e1545e93 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -199,16 +199,6 @@ const WidgetConfigResponse: WidgetConfigReducerState = { widgetName: "RadioGroup", version: 1, }, - ALERT_WIDGET: { - alertType: "NOTIFICATION", - intent: "SUCCESS", - rows: 3, - columns: 3, - header: "", - message: "", - widgetName: "Alert", - version: 1, - }, FILE_PICKER_WIDGET: { rows: 1, files: [], diff --git a/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx b/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx index 0a8b52b9f7..f5a6a93d8c 100644 --- a/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx +++ b/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx @@ -29,6 +29,7 @@ const PostBodyContainer = styled.div` const JSONEditorFieldWrapper = styled.div` margin: 0 30px; + width: 65%; .CodeMirror { height: auto; min-height: 250px; diff --git a/app/client/src/pages/Editor/PropertyPaneTitle.tsx b/app/client/src/pages/Editor/PropertyPaneTitle.tsx index 800ffd261b..83429616fd 100644 --- a/app/client/src/pages/Editor/PropertyPaneTitle.tsx +++ b/app/client/src/pages/Editor/PropertyPaneTitle.tsx @@ -121,7 +121,11 @@ const PropertyPaneTitle = memo((props: PropertyPaneTitleProps) => { const { title, updatePropertyTitle } = props; const updateNewTitle = useCallback( (value: string) => { - if (value && value.trim().length > 0 && value.trim() !== title.trim()) { + if ( + value && + value.trim().length > 0 && + value.trim() !== (title && title.trim()) + ) { updatePropertyTitle && updatePropertyTitle(value.trim()); } }, @@ -166,7 +170,9 @@ const PropertyPaneTitle = memo((props: PropertyPaneTitleProps) => { )} & WidgetConfigProps; SWITCH_WIDGET: Partial & WidgetConfigProps; RADIO_GROUP_WIDGET: Partial & WidgetConfigProps; - ALERT_WIDGET: Partial & WidgetConfigProps; FILE_PICKER_WIDGET: Partial & WidgetConfigProps; TABS_WIDGET: Partial> & WidgetConfigProps; diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 4907336c59..1a854827df 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -110,7 +110,7 @@ export function* createActionSaga( payload = merge(initialValues, actionPayload.payload); } - const response: ActionCreateUpdateResponse = yield ActionAPI.createAPI( + const response: ActionCreateUpdateResponse = yield ActionAPI.createAction( payload, ); const isValidResponse = yield validateResponse(response); @@ -267,7 +267,7 @@ export function* updateActionSaga(actionPayload: ReduxAction<{ id: string }>) { action = transformRestAction(action); } - const response: GenericApiResponse = yield ActionAPI.updateAPI( + const response: GenericApiResponse = yield ActionAPI.updateAction( action, ); const isValidResponse = yield validateResponse(response); @@ -424,7 +424,7 @@ function* copyActionSaga( pageId: action.payload.destinationPageId, }) as Partial; delete copyAction.id; - const response = yield ActionAPI.createAPI(copyAction); + const response = yield ActionAPI.createAction(copyAction); const datasources = yield select(getDataSources); const isValidResponse = yield validateResponse(response); @@ -486,6 +486,7 @@ export function* refactorActionName( // call to refactor action const refactorResponse = yield ActionAPI.updateActionName({ layoutId, + actionId: id, pageId: pageId, oldName: oldName, newName: newName, diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts index ed6b112718..4ebdc80958 100644 --- a/app/client/src/sagas/ApiPaneSagas.ts +++ b/app/client/src/sagas/ApiPaneSagas.ts @@ -508,12 +508,12 @@ function* handleApiNameChangeSuccessSaga( if (!actionObj) { // Error case, log to sentry Toaster.show({ - text: createMessage(ERROR_ACTION_RENAME_FAIL, actionObj.name), + text: createMessage(ERROR_ACTION_RENAME_FAIL, ""), variant: Variant.danger, }); Sentry.captureException( - new Error(createMessage(ERROR_ACTION_RENAME_FAIL, actionObj.name)), + new Error(createMessage(ERROR_ACTION_RENAME_FAIL, "")), { extra: { actionId: actionId, diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index ef3aba049b..71c32f408f 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -33,6 +33,8 @@ import { expandDatasourceEntity, fetchDatasourceStructure, createDatasourceFromForm, + updateDatasourceSuccess, + UpdateDatasourceSuccessAction, } from "actions/datasourceActions"; import { GenericApiResponse } from "api/ApiResponses"; import DatasourcesApi, { CreateDatasourceConfig } from "api/DatasourcesApi"; @@ -162,10 +164,10 @@ function* updateDatasourceSaga( const datasourceStruture = state.entities.datasources.structure[response.data.id]; - yield put({ - type: ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS, - payload: response.data, - }); + // Dont redirect if action payload has an onSuccess + yield put( + updateDatasourceSuccess(response.data, !actionPayload.onSuccess), + ); if (actionPayload.onSuccess) { yield put(actionPayload.onSuccess); } @@ -452,14 +454,15 @@ function* storeAsDatasourceSaga() { yield put(changeDatasource(createdDatasource)); } -function* updateDatasourceSuccessSaga(action: ReduxAction) { +function* updateDatasourceSuccessSaga(action: UpdateDatasourceSuccessAction) { const state = yield select(); const actionRouteInfo = _.get(state, "ui.datasourcePane.actionRouteInfo"); const updatedDatasource = action.payload; if ( actionRouteInfo && - updatedDatasource.id === actionRouteInfo.datasourceId + updatedDatasource.id === actionRouteInfo.datasourceId && + action.redirect ) { history.push( API_EDITOR_ID_URL( diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 26bb33222f..338fa02883 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -23,7 +23,6 @@ import { EvalErrorTypes, } from "utils/DynamicBindingUtils"; import log from "loglevel"; -import { WidgetType } from "constants/WidgetConstants"; import { WidgetProps } from "widgets/BaseWidget"; import PerformanceTracker, { PerformanceTransactionName, @@ -209,17 +208,17 @@ export function* clearEvalPropertyCacheOfWidget(widgetName: string) { } export function* validateProperty( - widgetType: WidgetType, property: string, value: any, props: WidgetProps, ) { + const unevalTree = yield select(getUnevaluatedDataTree); + const validation = unevalTree[props.widgetName].validationPaths[property]; return yield call(worker.request, EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY, { - widgetTypeConfigMap, - widgetType, property, value, props, + validation, }); } diff --git a/app/client/src/sagas/QueryPaneSagas.ts b/app/client/src/sagas/QueryPaneSagas.ts index f75ebdb792..fab337d732 100644 --- a/app/client/src/sagas/QueryPaneSagas.ts +++ b/app/client/src/sagas/QueryPaneSagas.ts @@ -160,12 +160,12 @@ function* handleNameChangeSuccessSaga( if (!actionObj) { // Error case, log to sentry Toaster.show({ - text: createMessage(ERROR_ACTION_RENAME_FAIL, actionObj.name), + text: createMessage(ERROR_ACTION_RENAME_FAIL, ""), variant: Variant.danger, }); Sentry.captureException( - new Error(createMessage(ERROR_ACTION_RENAME_FAIL, actionObj.name)), + new Error(createMessage(ERROR_ACTION_RENAME_FAIL, "")), { extra: { actionId: actionId, diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index adce7022ea..88a0fb3b0a 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -817,7 +817,6 @@ function* setWidgetDynamicPropertySaga( }); const { parsed } = yield call( validateProperty, - widget.type, propertyPath, propertyValue, widget, diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index 9be7d4aea8..e6d8176c85 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -262,6 +262,7 @@ const createLoadingWidget = ( ENTITY_TYPE: ENTITY_TYPE.WIDGET, bindingPaths: {}, triggerPaths: {}, + validationPaths: {}, isLoading: true, }; }; diff --git a/app/client/src/utils/WidgetFactory.tsx b/app/client/src/utils/WidgetFactory.tsx index 559933a04f..cc5418552b 100644 --- a/app/client/src/utils/WidgetFactory.tsx +++ b/app/client/src/utils/WidgetFactory.tsx @@ -5,10 +5,6 @@ import { WidgetDataProps, WidgetState, } from "widgets/BaseWidget"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "./WidgetValidation"; import React from "react"; import { PropertyPaneConfig, @@ -49,10 +45,6 @@ class WidgetFactory { WidgetType, WidgetBuilder > = new Map(); - static widgetPropValidationMap: Map< - WidgetType, - WidgetPropertyValidationType - > = new Map(); static widgetDerivedPropertiesGetterMap: Map< WidgetType, WidgetDerivedPropertyType @@ -74,14 +66,12 @@ class WidgetFactory { static registerWidgetBuilder( widgetType: WidgetType, widgetBuilder: WidgetBuilder, - widgetPropertyValidation: WidgetPropertyValidationType, derivedPropertiesMap: DerivedPropertiesMap, defaultPropertiesMap: Record, metaPropertiesMap: Record, propertyPaneConfig?: PropertyPaneConfig[], ) { this.widgetMap.set(widgetType, widgetBuilder); - this.widgetPropValidationMap.set(widgetType, widgetPropertyValidation); this.derivedPropertiesMap.set(widgetType, derivedPropertiesMap); this.defaultPropertiesMap.set(widgetType, defaultPropertiesMap); this.metaPropertiesMap.set(widgetType, metaPropertiesMap); @@ -122,17 +112,6 @@ class WidgetFactory { return Array.from(this.widgetMap.keys()); } - static getWidgetPropertyValidationMap( - widgetType: WidgetType, - ): WidgetPropertyValidationType { - const map = this.widgetPropValidationMap.get(widgetType); - if (!map) { - console.error("Widget type validation is not defined"); - return BASE_WIDGET_VALIDATION; - } - return map; - } - static getWidgetDerivedPropertiesMap( widgetType: WidgetType, ): DerivedPropertiesMap { @@ -181,7 +160,6 @@ class WidgetFactory { const typeConfigMap: WidgetTypeConfigMap = {}; WidgetFactory.getWidgetTypes().forEach((type) => { typeConfigMap[type] = { - validations: WidgetFactory.getWidgetPropertyValidationMap(type), defaultProperties: WidgetFactory.getWidgetDefaultPropertiesMap(type), derivedProperties: WidgetFactory.getWidgetDerivedPropertiesMap(type), metaProperties: WidgetFactory.getWidgetMetaPropertiesMap(type), @@ -194,7 +172,6 @@ class WidgetFactory { export type WidgetTypeConfigMap = Record< string, { - validations: WidgetPropertyValidationType; derivedProperties: WidgetDerivedPropertyType; defaultProperties: Record; metaProperties: Record; diff --git a/app/client/src/utils/WidgetRegistry.tsx b/app/client/src/utils/WidgetRegistry.tsx index 82776f5c62..9a27a44666 100644 --- a/app/client/src/utils/WidgetRegistry.tsx +++ b/app/client/src/utils/WidgetRegistry.tsx @@ -105,7 +105,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - ContainerWidget.getPropertyValidationMap(), ContainerWidget.getDerivedPropertiesMap(), ContainerWidget.getDefaultPropertiesMap(), ContainerWidget.getMetaPropertiesMap(), @@ -119,7 +118,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - TextWidget.getPropertyValidationMap(), TextWidget.getDerivedPropertiesMap(), TextWidget.getDefaultPropertiesMap(), TextWidget.getMetaPropertiesMap(), @@ -133,7 +131,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - ButtonWidget.getPropertyValidationMap(), ButtonWidget.getDerivedPropertiesMap(), ButtonWidget.getDefaultPropertiesMap(), ButtonWidget.getMetaPropertiesMap(), @@ -147,7 +144,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - InputWidget.getPropertyValidationMap(), InputWidget.getDerivedPropertiesMap(), InputWidget.getDefaultPropertiesMap(), InputWidget.getMetaPropertiesMap(), @@ -161,7 +157,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - CheckboxWidget.getPropertyValidationMap(), CheckboxWidget.getDerivedPropertiesMap(), CheckboxWidget.getDefaultPropertiesMap(), CheckboxWidget.getMetaPropertiesMap(), @@ -175,7 +170,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - SwitchWidget.getPropertyValidationMap(), SwitchWidget.getDerivedPropertiesMap(), SwitchWidget.getDefaultPropertiesMap(), SwitchWidget.getMetaPropertiesMap(), @@ -189,7 +183,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - DropdownWidget.getPropertyValidationMap(), DropdownWidget.getDerivedPropertiesMap(), DropdownWidget.getDefaultPropertiesMap(), DropdownWidget.getMetaPropertiesMap(), @@ -203,7 +196,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - RadioGroupWidget.getPropertyValidationMap(), RadioGroupWidget.getDerivedPropertiesMap(), RadioGroupWidget.getDefaultPropertiesMap(), RadioGroupWidget.getMetaPropertiesMap(), @@ -217,7 +209,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - ImageWidget.getPropertyValidationMap(), ImageWidget.getDerivedPropertiesMap(), ImageWidget.getDefaultPropertiesMap(), ImageWidget.getMetaPropertiesMap(), @@ -230,7 +221,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - TableWidget.getPropertyValidationMap(), TableWidget.getDerivedPropertiesMap(), TableWidget.getDefaultPropertiesMap(), TableWidget.getMetaPropertiesMap(), @@ -244,7 +234,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - VideoWidget.getPropertyValidationMap(), VideoWidget.getDerivedPropertiesMap(), VideoWidget.getDefaultPropertiesMap(), VideoWidget.getMetaPropertiesMap(), @@ -258,7 +247,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - FilePickerWidget.getPropertyValidationMap(), FilePickerWidget.getDerivedPropertiesMap(), FilePickerWidget.getDefaultPropertiesMap(), FilePickerWidget.getMetaPropertiesMap(), @@ -271,7 +259,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - DatePickerWidget.getPropertyValidationMap(), DatePickerWidget.getDerivedPropertiesMap(), DatePickerWidget.getDefaultPropertiesMap(), DatePickerWidget.getMetaPropertiesMap(), @@ -284,7 +271,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - DatePickerWidget2.getPropertyValidationMap(), DatePickerWidget2.getDerivedPropertiesMap(), DatePickerWidget2.getDefaultPropertiesMap(), DatePickerWidget2.getMetaPropertiesMap(), @@ -299,7 +285,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - TabsWidget.getPropertyValidationMap(), TabsWidget.getDerivedPropertiesMap(), TabsWidget.getDefaultPropertiesMap(), TabsWidget.getMetaPropertiesMap(), @@ -312,7 +297,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - BaseWidget.getPropertyValidationMap(), BaseWidget.getDerivedPropertiesMap(), BaseWidget.getDefaultPropertiesMap(), BaseWidget.getMetaPropertiesMap(), @@ -325,7 +309,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - RichTextEditorWidget.getPropertyValidationMap(), RichTextEditorWidget.getDerivedPropertiesMap(), RichTextEditorWidget.getDefaultPropertiesMap(), RichTextEditorWidget.getMetaPropertiesMap(), @@ -338,7 +321,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - ChartWidget.getPropertyValidationMap(), ChartWidget.getDerivedPropertiesMap(), ChartWidget.getDefaultPropertiesMap(), ChartWidget.getMetaPropertiesMap(), @@ -353,7 +335,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - FormWidget.getPropertyValidationMap(), FormWidget.getDerivedPropertiesMap(), FormWidget.getDefaultPropertiesMap(), FormWidget.getMetaPropertiesMap(), @@ -367,7 +348,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - FormButtonWidget.getPropertyValidationMap(), FormButtonWidget.getDerivedPropertiesMap(), FormButtonWidget.getDefaultPropertiesMap(), FormButtonWidget.getMetaPropertiesMap(), @@ -381,7 +361,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - MapWidget.getPropertyValidationMap(), MapWidget.getDerivedPropertiesMap(), MapWidget.getDefaultPropertiesMap(), MapWidget.getMetaPropertiesMap(), @@ -397,7 +376,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - CanvasWidget.getPropertyValidationMap(), CanvasWidget.getDerivedPropertiesMap(), CanvasWidget.getDefaultPropertiesMap(), CanvasWidget.getMetaPropertiesMap(), @@ -411,7 +389,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - IconWidget.getPropertyValidationMap(), IconWidget.getDerivedPropertiesMap(), IconWidget.getDefaultPropertiesMap(), IconWidget.getMetaPropertiesMap(), @@ -425,7 +402,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - SkeletonWidget.getPropertyValidationMap(), SkeletonWidget.getDerivedPropertiesMap(), SkeletonWidget.getDefaultPropertiesMap(), SkeletonWidget.getMetaPropertiesMap(), @@ -439,7 +415,6 @@ export default class WidgetBuilderRegistry { return ; }, }, - ModalWidget.getPropertyValidationMap(), ModalWidget.getDerivedPropertiesMap(), ModalWidget.getDefaultPropertiesMap(), ModalWidget.getMetaPropertiesMap(), diff --git a/app/client/src/utils/WidgetValidation.ts b/app/client/src/utils/WidgetValidation.ts deleted file mode 100644 index a45d083981..0000000000 --- a/app/client/src/utils/WidgetValidation.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { - VALIDATION_TYPES, - ValidationType, - Validator, -} from "constants/WidgetValidation"; - -export const BASE_WIDGET_VALIDATION = { - isLoading: VALIDATION_TYPES.BOOLEAN, - isVisible: VALIDATION_TYPES.BOOLEAN, - isDisabled: VALIDATION_TYPES.BOOLEAN, -}; - -export type WidgetPropertyValidationType = Record< - string, - ValidationType | Validator ->; diff --git a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts index 853bb76e67..bff6e9a8a5 100644 --- a/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts +++ b/app/client/src/utils/autocomplete/dataTreeTypeDefCreator.test.ts @@ -31,6 +31,7 @@ describe("dataTreeTypeDefCreator", () => { triggerPaths: { onTextChange: true, }, + validationPaths: {}, }, }; const def = dataTreeTypeDefCreator(dataTree); diff --git a/app/client/src/widgets/AlertWidget.tsx b/app/client/src/widgets/AlertWidget.tsx deleted file mode 100644 index 50a3c915c3..0000000000 --- a/app/client/src/widgets/AlertWidget.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { Component } from "react"; -import { WidgetProps } from "./BaseWidget"; - -class AlertWidget extends Component { - getPageView() { - return

; - } -} - -export type AlertType = "DIALOG" | "NOTIFICATION"; -export type MessageIntent = "SUCCESS" | "ERROR" | "INFO" | "WARNING"; - -export interface AlertWidgetProps extends WidgetProps { - alertType: AlertType; - intent: MessageIntent; - header: string; - message: string; -} - -export default AlertWidget; diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index d3454c99f7..4bc1a15ea2 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -24,10 +24,6 @@ import shallowequal from "shallowequal"; import { PositionTypes } from "constants/WidgetConstants"; import { EditorContext } from "components/editorComponents/EditorContextProvider"; import ErrorBoundary from "components/editorComponents/ErrorBoundry"; -import { - BASE_WIDGET_VALIDATION, - WidgetPropertyValidationType, -} from "utils/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import { WidgetDynamicPathListProps, @@ -57,11 +53,6 @@ abstract class BaseWidget< static getPropertyPaneConfig(): PropertyPaneConfig[] { return []; } - // Needed to send a default no validation option. In case a widget needs - // validation implement this in the widget class again - static getPropertyValidationMap(): WidgetPropertyValidationType { - return BASE_WIDGET_VALIDATION; - } static getDerivedPropertiesMap(): DerivedPropertiesMap { return {}; diff --git a/app/client/src/widgets/ButtonWidget.tsx b/app/client/src/widgets/ButtonWidget.tsx index 57bb53fdd1..5adb5df62e 100644 --- a/app/client/src/widgets/ButtonWidget.tsx +++ b/app/client/src/widgets/ButtonWidget.tsx @@ -5,10 +5,6 @@ import ButtonComponent, { ButtonType, } from "components/designSystems/blueprint/ButtonComponent"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import * as Sentry from "@sentry/react"; import withMeta, { WithMeta } from "./MetaHOC"; @@ -38,6 +34,7 @@ class ButtonWidget extends BaseWidget { placeholderText: "Enter label text", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "buttonStyle", @@ -69,6 +66,7 @@ class ButtonWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isDisabled", @@ -78,6 +76,7 @@ class ButtonWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "googleRecaptchaKey", @@ -87,6 +86,7 @@ class ButtonWidget extends BaseWidget { placeholderText: "Enter google recaptcha key", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, ], }, @@ -107,14 +107,6 @@ class ButtonWidget extends BaseWidget { ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - text: VALIDATION_TYPES.TEXT, - buttonStyle: VALIDATION_TYPES.TEXT, - // onClick: VALIDATION_TYPES.ACTION_SELECTOR, - }; - } static getMetaPropertiesMap(): Record { return { recaptchaToken: undefined, diff --git a/app/client/src/widgets/ChartWidget/index.tsx b/app/client/src/widgets/ChartWidget/index.tsx index 2c479d2154..a7c1f18b88 100644 --- a/app/client/src/widgets/ChartWidget/index.tsx +++ b/app/client/src/widgets/ChartWidget/index.tsx @@ -1,8 +1,6 @@ import React, { lazy, Suspense } from "react"; import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; -import { WidgetPropertyValidationType } from "utils/WidgetValidation"; -import { VALIDATION_TYPES } from "constants/WidgetValidation"; import Skeleton from "components/utils/Skeleton"; import * as Sentry from "@sentry/react"; import { retryPromise } from "utils/AppsmithUtils"; @@ -20,17 +18,6 @@ const ChartComponent = lazy(() => ); class ChartWidget extends BaseWidget { - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - xAxisName: VALIDATION_TYPES.TEXT, - yAxisName: VALIDATION_TYPES.TEXT, - chartName: VALIDATION_TYPES.TEXT, - isVisible: VALIDATION_TYPES.BOOLEAN, - chartData: VALIDATION_TYPES.CHART_DATA, - customFusionChartConfig: VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA, - }; - } - static getMetaPropertiesMap(): Record { return { selectedDataPoint: undefined, diff --git a/app/client/src/widgets/ChartWidget/propertyConfig.ts b/app/client/src/widgets/ChartWidget/propertyConfig.ts index e2e47d5dbc..6a16419166 100644 --- a/app/client/src/widgets/ChartWidget/propertyConfig.ts +++ b/app/client/src/widgets/ChartWidget/propertyConfig.ts @@ -1,4 +1,5 @@ import { ChartWidgetProps } from "widgets/ChartWidget"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; export default [ { @@ -12,6 +13,7 @@ export default [ controlType: "INPUT_TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Changes the visualisation of the chart data", @@ -56,6 +58,7 @@ export default [ isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -69,6 +72,7 @@ export default [ isBindProperty: true, isTriggerProperty: false, hidden: (x: any) => x.chartType !== "CUSTOM_FUSION_CHART", + validation: VALIDATION_TYPES.CUSTOM_FUSION_CHARTS_DATA, }, { sectionName: "Chart Data", @@ -92,6 +96,7 @@ export default [ controlType: "INPUT_TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Series data", @@ -100,6 +105,7 @@ export default [ controlType: "INPUT_TEXT_AREA", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.CHART_DATA, }, ], }, @@ -118,6 +124,7 @@ export default [ controlType: "INPUT_TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Specifies the label of the y-axis", @@ -127,6 +134,7 @@ export default [ controlType: "INPUT_TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Enables scrolling inside the chart", diff --git a/app/client/src/widgets/CheckboxWidget.tsx b/app/client/src/widgets/CheckboxWidget.tsx index 361ab3df63..ec99b3b486 100644 --- a/app/client/src/widgets/CheckboxWidget.tsx +++ b/app/client/src/widgets/CheckboxWidget.tsx @@ -4,10 +4,6 @@ import { WidgetType } from "constants/WidgetConstants"; import CheckboxComponent from "components/designSystems/blueprint/CheckboxComponent"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import * as Sentry from "@sentry/react"; import withMeta, { WithMeta } from "./MetaHOC"; @@ -27,6 +23,7 @@ class CheckboxWidget extends BaseWidget { placeholderText: "Enter label text", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "alignWidget", @@ -55,6 +52,7 @@ class CheckboxWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isRequired", @@ -64,6 +62,7 @@ class CheckboxWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isVisible", @@ -73,6 +72,7 @@ class CheckboxWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isDisabled", @@ -82,6 +82,7 @@ class CheckboxWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -101,14 +102,6 @@ class CheckboxWidget extends BaseWidget { }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - label: VALIDATION_TYPES.TEXT, - defaultCheckedState: VALIDATION_TYPES.BOOLEAN, - // onCheckChange: VALIDATION_TYPES.ACTION_SELECTOR, - }; - } static getDefaultPropertiesMap(): Record { return { diff --git a/app/client/src/widgets/ContainerWidget.tsx b/app/client/src/widgets/ContainerWidget.tsx index 0d3d7f56ca..39667e11cb 100644 --- a/app/client/src/widgets/ContainerWidget.tsx +++ b/app/client/src/widgets/ContainerWidget.tsx @@ -14,6 +14,7 @@ import { import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import * as Sentry from "@sentry/react"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; class ContainerWidget extends BaseWidget< ContainerWidgetProps, @@ -37,6 +38,7 @@ class ContainerWidget extends BaseWidget< controlType: "INPUT_TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Controls the visibility of the widget", @@ -46,6 +48,7 @@ class ContainerWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "shouldScrollContents", diff --git a/app/client/src/widgets/DatePickerWidget.tsx b/app/client/src/widgets/DatePickerWidget.tsx index ff3651239d..8cf5748e77 100644 --- a/app/client/src/widgets/DatePickerWidget.tsx +++ b/app/client/src/widgets/DatePickerWidget.tsx @@ -3,10 +3,6 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import * as Sentry from "@sentry/react"; @@ -29,6 +25,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.DEFAULT_DATE, }, { helpText: "Sets the format of the selected date", @@ -60,6 +57,7 @@ class DatePickerWidget extends BaseWidget { ], isBindProperty: true, isTriggerProperty: false, + dateFormat: VALIDATION_TYPES.TEXT, }, { propertyName: "isRequired", @@ -69,6 +67,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isVisible", @@ -78,6 +77,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isDisabled", @@ -87,6 +87,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "minDate", @@ -96,6 +97,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.MIN_DATE, }, { propertyName: "maxDate", @@ -105,6 +107,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.MAX_DATE, }, ], }, @@ -123,22 +126,6 @@ class DatePickerWidget extends BaseWidget { }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - defaultDate: VALIDATION_TYPES.DEFAULT_DATE, - timezone: VALIDATION_TYPES.TEXT, - enableTimePicker: VALIDATION_TYPES.BOOLEAN, - dateFormat: VALIDATION_TYPES.TEXT, - label: VALIDATION_TYPES.TEXT, - datePickerType: VALIDATION_TYPES.TEXT, - maxDate: VALIDATION_TYPES.MAX_DATE, - minDate: VALIDATION_TYPES.MIN_DATE, - isRequired: VALIDATION_TYPES.BOOLEAN, - // onDateSelected: VALIDATION_TYPES.ACTION_SELECTOR, - // onDateRangeSelected: VALIDATION_TYPES.ACTION_SELECTOR, - }; - } static getDerivedPropertiesMap(): DerivedPropertiesMap { return { diff --git a/app/client/src/widgets/DatePickerWidget2.tsx b/app/client/src/widgets/DatePickerWidget2.tsx index ab0528b4d9..6a98b7d0bf 100644 --- a/app/client/src/widgets/DatePickerWidget2.tsx +++ b/app/client/src/widgets/DatePickerWidget2.tsx @@ -3,10 +3,6 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent2"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import * as Sentry from "@sentry/react"; @@ -30,6 +26,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.DEFAULT_DATE, }, { helpText: "Sets the format of the selected date", @@ -122,6 +119,7 @@ class DatePickerWidget extends BaseWidget { ], isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "isRequired", @@ -131,6 +129,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isVisible", @@ -140,6 +139,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isDisabled", @@ -149,6 +149,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "minDate", @@ -159,6 +160,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.DATE_ISO_STRING, }, { propertyName: "maxDate", @@ -169,6 +171,7 @@ class DatePickerWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.DATE_ISO_STRING, }, ], }, @@ -188,23 +191,6 @@ class DatePickerWidget extends BaseWidget { ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - defaultDate: VALIDATION_TYPES.DEFAULT_DATE, - timezone: VALIDATION_TYPES.TEXT, - enableTimePicker: VALIDATION_TYPES.BOOLEAN, - dateFormat: VALIDATION_TYPES.TEXT, - label: VALIDATION_TYPES.TEXT, - datePickerType: VALIDATION_TYPES.TEXT, - maxDate: VALIDATION_TYPES.DATE_ISO_STRING, - minDate: VALIDATION_TYPES.DATE_ISO_STRING, - isRequired: VALIDATION_TYPES.BOOLEAN, - // onDateSelected: VALIDATION_TYPES.ACTION_SELECTOR, - // onDateRangeSelected: VALIDATION_TYPES.ACTION_SELECTOR, - }; - } - static getDerivedPropertiesMap(): DerivedPropertiesMap { return { isValid: `{{ this.isRequired ? !!this.selectedDate : true }}`, diff --git a/app/client/src/widgets/DropdownWidget.tsx b/app/client/src/widgets/DropdownWidget.tsx index d4adbd943b..cf56553bab 100644 --- a/app/client/src/widgets/DropdownWidget.tsx +++ b/app/client/src/widgets/DropdownWidget.tsx @@ -4,10 +4,6 @@ import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import DropDownComponent from "components/designSystems/blueprint/DropdownComponent"; import _ from "lodash"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { Intent as BlueprintIntent } from "@blueprintjs/core"; import * as Sentry from "@sentry/react"; @@ -48,6 +44,7 @@ class DropdownWidget extends BaseWidget { placeholderText: 'Enter [{label: "label1", value: "value2"}]', isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.OPTIONS_DATA, }, { helpText: "Selects the option with value by default", @@ -57,6 +54,7 @@ class DropdownWidget extends BaseWidget { placeholderText: "Enter option value", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.DEFAULT_OPTION_VALUE, }, { propertyName: "isRequired", @@ -66,6 +64,7 @@ class DropdownWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { helpText: "Controls the visibility of the widget", @@ -75,6 +74,7 @@ class DropdownWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isDisabled", @@ -84,6 +84,7 @@ class DropdownWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -103,21 +104,6 @@ class DropdownWidget extends BaseWidget { }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - placeholderText: VALIDATION_TYPES.TEXT, - label: VALIDATION_TYPES.TEXT, - options: VALIDATION_TYPES.OPTIONS_DATA, - selectionType: VALIDATION_TYPES.TEXT, - isRequired: VALIDATION_TYPES.BOOLEAN, - // onOptionChange: VALIDATION_TYPES.ACTION_SELECTOR, - selectedOptionValues: VALIDATION_TYPES.ARRAY, - selectedOptionLabels: VALIDATION_TYPES.ARRAY, - selectedOptionLabel: VALIDATION_TYPES.TEXT, - defaultOptionValue: VALIDATION_TYPES.DEFAULT_OPTION_VALUE, - }; - } static getDerivedPropertiesMap() { return { diff --git a/app/client/src/widgets/FilepickerWidget.tsx b/app/client/src/widgets/FilepickerWidget.tsx index e3ed4694b3..c5de24c597 100644 --- a/app/client/src/widgets/FilepickerWidget.tsx +++ b/app/client/src/widgets/FilepickerWidget.tsx @@ -7,10 +7,6 @@ import GoogleDrive from "@uppy/google-drive"; import Webcam from "@uppy/webcam"; import Url from "@uppy/url"; import OneDrive from "@uppy/onedrive"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { EventType, @@ -50,6 +46,7 @@ class FilePickerWidget extends BaseWidget< inputType: "TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "maxNumFiles", @@ -61,6 +58,7 @@ class FilePickerWidget extends BaseWidget< inputType: "INTEGER", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.NUMBER, }, { propertyName: "maxFileSize", @@ -115,6 +113,7 @@ class FilePickerWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.ARRAY, }, { helpText: "Set the format of the data read from the files", @@ -146,6 +145,7 @@ class FilePickerWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isVisible", @@ -155,6 +155,7 @@ class FilePickerWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "uploadedFileUrlPaths", @@ -175,6 +176,7 @@ class FilePickerWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -195,17 +197,6 @@ class FilePickerWidget extends BaseWidget< }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - label: VALIDATION_TYPES.TEXT, - maxNumFiles: VALIDATION_TYPES.NUMBER, - allowedFileTypes: VALIDATION_TYPES.ARRAY, - selectedFiles: VALIDATION_TYPES.ARRAY, - isRequired: VALIDATION_TYPES.BOOLEAN, - // onFilesSelected: VALIDATION_TYPES.ACTION_SELECTOR, - }; - } static getDerivedPropertiesMap(): DerivedPropertiesMap { return { diff --git a/app/client/src/widgets/FormButtonWidget.tsx b/app/client/src/widgets/FormButtonWidget.tsx index 10fd49fe5f..6afa8477dd 100644 --- a/app/client/src/widgets/FormButtonWidget.tsx +++ b/app/client/src/widgets/FormButtonWidget.tsx @@ -8,10 +8,6 @@ import { EventType, ExecutionResult, } from "constants/AppsmithActionConstants/ActionConstants"; -import { - BASE_WIDGET_VALIDATION, - WidgetPropertyValidationType, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import * as Sentry from "@sentry/react"; import withMeta, { WithMeta } from "./MetaHOC"; @@ -45,6 +41,7 @@ class FormButtonWidget extends BaseWidget< placeholderText: "Enter label text", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "buttonStyle", @@ -86,6 +83,7 @@ class FormButtonWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isVisible", @@ -95,6 +93,7 @@ class FormButtonWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "googleRecaptchaKey", @@ -104,6 +103,7 @@ class FormButtonWidget extends BaseWidget< placeholderText: "Enter google recaptcha key", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, ], }, @@ -130,16 +130,6 @@ class FormButtonWidget extends BaseWidget< }; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - text: VALIDATION_TYPES.TEXT, - disabledWhenInvalid: VALIDATION_TYPES.BOOLEAN, - buttonStyle: VALIDATION_TYPES.TEXT, - buttonType: VALIDATION_TYPES.TEXT, - }; - } - clickWithRecaptcha(token: string) { if (this.props.onClick) { this.setState({ diff --git a/app/client/src/widgets/FormWidget.tsx b/app/client/src/widgets/FormWidget.tsx index 80f3889d1b..dfc2450c0e 100644 --- a/app/client/src/widgets/FormWidget.tsx +++ b/app/client/src/widgets/FormWidget.tsx @@ -6,6 +6,7 @@ import ContainerWidget, { ContainerWidgetProps } from "widgets/ContainerWidget"; import { ContainerComponentProps } from "components/designSystems/appsmith/ContainerComponent"; import * as Sentry from "@sentry/react"; import withMeta from "./MetaHOC"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; class FormWidget extends ContainerWidget { static getPropertyPaneConfig() { @@ -21,6 +22,7 @@ class FormWidget extends ContainerWidget { controlType: "INPUT_TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Controls the visibility of the widget", @@ -30,6 +32,7 @@ class FormWidget extends ContainerWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "shouldScrollContents", diff --git a/app/client/src/widgets/ImageWidget.tsx b/app/client/src/widgets/ImageWidget.tsx index 3eb5fcfe6d..b285fb4ade 100644 --- a/app/client/src/widgets/ImageWidget.tsx +++ b/app/client/src/widgets/ImageWidget.tsx @@ -2,10 +2,6 @@ import * as React from "react"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType, RenderModes } from "constants/WidgetConstants"; import ImageComponent from "components/designSystems/appsmith/ImageComponent"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import * as Sentry from "@sentry/react"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; @@ -28,6 +24,7 @@ class ImageWidget extends BaseWidget { placeholderText: "Enter URL / Base64", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Renders the url or Base64 when no image is provided", @@ -37,6 +34,7 @@ class ImageWidget extends BaseWidget { placeholderText: "Enter URL / Base64", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Controls the visibility of the widget", @@ -46,6 +44,7 @@ class ImageWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { helpText: "Controls the max zoom of the widget", @@ -77,6 +76,7 @@ class ImageWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.NUMBER, }, ], }, @@ -97,15 +97,6 @@ class ImageWidget extends BaseWidget { }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - image: VALIDATION_TYPES.TEXT, - imageShape: VALIDATION_TYPES.TEXT, - defaultImage: VALIDATION_TYPES.TEXT, - maxZoomLevel: VALIDATION_TYPES.NUMBER, - }; - } getPageView() { const { maxZoomLevel } = this.props; return ( diff --git a/app/client/src/widgets/InputWidget.tsx b/app/client/src/widgets/InputWidget.tsx index 12c6b8af8d..0dfa1550ef 100644 --- a/app/client/src/widgets/InputWidget.tsx +++ b/app/client/src/widgets/InputWidget.tsx @@ -8,10 +8,6 @@ import { EventType, ExecutionResult, } from "constants/AppsmithActionConstants/ActionConstants"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { createMessage, FIELD_REQUIRED_ERROR } from "constants/messages"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; @@ -65,6 +61,7 @@ class InputWidget extends BaseWidget { placeholderText: "Enter default text", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Sets a placeholder text for the input", @@ -74,6 +71,7 @@ class InputWidget extends BaseWidget { placeholderText: "Enter placeholder text", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: @@ -85,6 +83,7 @@ class InputWidget extends BaseWidget { inputType: "TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.REGEX, }, { helpText: @@ -96,6 +95,7 @@ class InputWidget extends BaseWidget { inputType: "TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "isRequired", @@ -105,6 +105,7 @@ class InputWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { helpText: "Controls the visibility of the widget", @@ -114,6 +115,7 @@ class InputWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { helpText: "Disables input to this widget", @@ -123,6 +125,7 @@ class InputWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { helpText: "Clears the input value after submit", @@ -132,6 +135,7 @@ class InputWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -161,29 +165,6 @@ class InputWidget extends BaseWidget { }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - inputType: VALIDATION_TYPES.TEXT, - defaultText: VALIDATION_TYPES.TEXT, - isDisabled: VALIDATION_TYPES.BOOLEAN, - text: VALIDATION_TYPES.TEXT, - regex: VALIDATION_TYPES.REGEX, - errorMessage: VALIDATION_TYPES.TEXT, - placeholderText: VALIDATION_TYPES.TEXT, - maxChars: VALIDATION_TYPES.NUMBER, - minNum: VALIDATION_TYPES.NUMBER, - maxNum: VALIDATION_TYPES.NUMBER, - label: VALIDATION_TYPES.TEXT, - inputValidators: VALIDATION_TYPES.ARRAY, - focusIndex: VALIDATION_TYPES.NUMBER, - isAutoFocusEnabled: VALIDATION_TYPES.BOOLEAN, - // onTextChanged: VALIDATION_TYPES.ACTION_SELECTOR, - isRequired: VALIDATION_TYPES.BOOLEAN, - isValid: VALIDATION_TYPES.BOOLEAN, - resetOnSubmit: VALIDATION_TYPES.BOOLEAN, - }; - } static getDerivedPropertiesMap(): DerivedPropertiesMap { return { diff --git a/app/client/src/widgets/MapWidget.tsx b/app/client/src/widgets/MapWidget.tsx index 12d28a472b..c9c30443fc 100644 --- a/app/client/src/widgets/MapWidget.tsx +++ b/app/client/src/widgets/MapWidget.tsx @@ -2,7 +2,6 @@ import React from "react"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import MapComponent from "components/designSystems/appsmith/MapComponent"; -import { WidgetPropertyValidationType } from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { getAppsmithConfigs } from "configs"; @@ -52,6 +51,7 @@ class MapWidget extends BaseWidget { controlType: "LOCATION_SEARCH", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.LAT_LONG, }, { propertyName: "defaultMarkers", @@ -62,6 +62,7 @@ class MapWidget extends BaseWidget { placeholderText: 'Enter [{ "lat": "val1", "long": "val2" }]', isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.MARKERS, }, { propertyName: "enableSearch", @@ -104,6 +105,7 @@ class MapWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -130,19 +132,6 @@ class MapWidget extends BaseWidget { }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - defaultMarkers: VALIDATION_TYPES.MARKERS, - isDisabled: VALIDATION_TYPES.BOOLEAN, - isVisible: VALIDATION_TYPES.BOOLEAN, - enableSearch: VALIDATION_TYPES.BOOLEAN, - enablePickLocation: VALIDATION_TYPES.BOOLEAN, - enableCreateMarker: VALIDATION_TYPES.BOOLEAN, - allowZoom: VALIDATION_TYPES.BOOLEAN, - zoomLevel: VALIDATION_TYPES.NUMBER, - mapCenter: VALIDATION_TYPES.LAT_LONG, - }; - } static getDefaultPropertiesMap(): Record { return { diff --git a/app/client/src/widgets/RadioGroupWidget.tsx b/app/client/src/widgets/RadioGroupWidget.tsx index 1cc37328eb..a5334611af 100644 --- a/app/client/src/widgets/RadioGroupWidget.tsx +++ b/app/client/src/widgets/RadioGroupWidget.tsx @@ -3,10 +3,6 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import RadioGroupComponent from "components/designSystems/blueprint/RadioGroupComponent"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import * as Sentry from "@sentry/react"; import withMeta, { WithMeta } from "./MetaHOC"; @@ -26,6 +22,7 @@ class RadioGroupWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.OPTIONS_DATA, }, { helpText: "Selects a value of the options entered by default", @@ -35,6 +32,7 @@ class RadioGroupWidget extends BaseWidget { controlType: "INPUT_TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "isRequired", @@ -44,6 +42,7 @@ class RadioGroupWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { helpText: "Controls the visibility of the widget", @@ -53,6 +52,7 @@ class RadioGroupWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isDisabled", @@ -62,6 +62,7 @@ class RadioGroupWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -82,17 +83,6 @@ class RadioGroupWidget extends BaseWidget { }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - label: VALIDATION_TYPES.TEXT, - options: VALIDATION_TYPES.OPTIONS_DATA, - selectedOptionValue: VALIDATION_TYPES.TEXT, - defaultOptionValue: VALIDATION_TYPES.TEXT, - isRequired: VALIDATION_TYPES.BOOLEAN, - // onSelectionChange: VALIDATION_TYPES.ACTION_SELECTOR, - }; - } static getDerivedPropertiesMap() { return { selectedOption: diff --git a/app/client/src/widgets/RichTextEditorWidget.tsx b/app/client/src/widgets/RichTextEditorWidget.tsx index 86b85a5361..5957e647dc 100644 --- a/app/client/src/widgets/RichTextEditorWidget.tsx +++ b/app/client/src/widgets/RichTextEditorWidget.tsx @@ -2,7 +2,6 @@ import React, { lazy, Suspense } from "react"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; -import { WidgetPropertyValidationType } from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import Skeleton from "components/utils/Skeleton"; @@ -60,6 +59,7 @@ class RichTextEditorWidget extends BaseWidget< placeholderText: "Enter HTML", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "isVisible", @@ -69,6 +69,7 @@ class RichTextEditorWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isDisabled", @@ -78,6 +79,7 @@ class RichTextEditorWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -97,14 +99,6 @@ class RichTextEditorWidget extends BaseWidget< }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - placeholder: VALIDATION_TYPES.TEXT, - defaultText: VALIDATION_TYPES.TEXT, - isDisabled: VALIDATION_TYPES.BOOLEAN, - isVisible: VALIDATION_TYPES.BOOLEAN, - }; - } static getMetaPropertiesMap(): Record { return { diff --git a/app/client/src/widgets/SwitchWidget.tsx b/app/client/src/widgets/SwitchWidget.tsx index 898b048772..e64223c7fd 100644 --- a/app/client/src/widgets/SwitchWidget.tsx +++ b/app/client/src/widgets/SwitchWidget.tsx @@ -3,10 +3,6 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import * as Sentry from "@sentry/react"; import withMeta, { WithMeta } from "./MetaHOC"; -import { - BASE_WIDGET_VALIDATION, - WidgetPropertyValidationType, -} from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import { SwitchComponent } from "components/designSystems/blueprint/SwitchComponent"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; @@ -26,6 +22,7 @@ class SwitchWidget extends BaseWidget { placeholderText: "Enter label text", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "alignWidget", @@ -54,6 +51,7 @@ class SwitchWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isVisible", @@ -63,6 +61,7 @@ class SwitchWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "isDisabled", @@ -72,6 +71,7 @@ class SwitchWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -110,14 +110,6 @@ class SwitchWidget extends BaseWidget { return "SWITCH_WIDGET"; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - label: VALIDATION_TYPES.TEXT, - defaultSwitchState: VALIDATION_TYPES.BOOLEAN, - }; - } - static getDefaultPropertiesMap(): Record { return { isSwitchedOn: "defaultSwitchState", diff --git a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts index d2ae80c08e..2273b8ed17 100644 --- a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts +++ b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts @@ -2,6 +2,7 @@ import { get } from "lodash"; import { Colors } from "constants/Colors"; import { ColumnProperties } from "components/designSystems/appsmith/TableComponent/Constants"; import { TableWidgetProps } from "./TableWidgetConstants"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; // A hook to update all column styles when global table styles are updated const updateColumnStyles = ( @@ -142,6 +143,7 @@ export default [ inputType: "ARRAY", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TABLE_DATA, }, { helpText: "Columns", @@ -637,6 +639,7 @@ export default [ placeholderText: "Enter default search text", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { helpText: "Selects the default selected row", @@ -646,6 +649,7 @@ export default [ placeholderText: "Enter row index", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.DEFAULT_SELECTED_ROW, }, { helpText: @@ -664,6 +668,7 @@ export default [ controlType: "SWITCH", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { propertyName: "multiRowSelection", diff --git a/app/client/src/widgets/TableWidget/index.tsx b/app/client/src/widgets/TableWidget/index.tsx index 77aaa693fe..1f96a11805 100644 --- a/app/client/src/widgets/TableWidget/index.tsx +++ b/app/client/src/widgets/TableWidget/index.tsx @@ -10,11 +10,6 @@ import { renderActions, } from "components/designSystems/appsmith/TableComponent/TableUtilities"; import { getAllTableColumnKeys } from "components/designSystems/appsmith/TableComponent/TableHelpers"; -import { VALIDATION_TYPES } from "constants/WidgetValidation"; -import { - BASE_WIDGET_VALIDATION, - WidgetPropertyValidationType, -} from "utils/WidgetValidation"; import Skeleton from "components/utils/Skeleton"; import moment from "moment"; import { isNumber, isString, isNil, isEqual, xor, without } from "lodash"; @@ -45,22 +40,6 @@ const ReactTableComponent = lazy(() => ); class TableWidget extends BaseWidget { - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - tableData: VALIDATION_TYPES.TABLE_DATA, - nextPageKey: VALIDATION_TYPES.TEXT, - prevPageKey: VALIDATION_TYPES.TEXT, - label: VALIDATION_TYPES.TEXT, - searchText: VALIDATION_TYPES.TEXT, - defaultSearchText: VALIDATION_TYPES.TEXT, - defaultSelectedRow: VALIDATION_TYPES.DEFAULT_SELECTED_ROW, - pageSize: VALIDATION_TYPES.NUMBER, - selectedRowIndices: VALIDATION_TYPES.ROW_INDICES, - pageNo: VALIDATION_TYPES.TABLE_PAGE_NO, - }; - } - static getPropertyPaneConfig() { return tablePropertyPaneConfig; } diff --git a/app/client/src/widgets/TabsWidget.tsx b/app/client/src/widgets/TabsWidget.tsx index a9159423d8..4e123ebee4 100644 --- a/app/client/src/widgets/TabsWidget.tsx +++ b/app/client/src/widgets/TabsWidget.tsx @@ -3,7 +3,6 @@ import TabsComponent from "components/designSystems/appsmith/TabsComponent"; import { WidgetType, WidgetTypes } from "constants/WidgetConstants"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import WidgetFactory from "utils/WidgetFactory"; -import { WidgetPropertyValidationType } from "utils/WidgetValidation"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; import _ from "lodash"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; @@ -29,6 +28,7 @@ class TabsWidget extends BaseWidget< controlType: "TABS_INPUT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TABS_DATA, }, { propertyName: "defaultTab", @@ -38,6 +38,7 @@ class TabsWidget extends BaseWidget< controlType: "INPUT_TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.SELECTED_TAB, }, { propertyName: "shouldShowTabs", @@ -63,6 +64,7 @@ class TabsWidget extends BaseWidget< isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -82,12 +84,6 @@ class TabsWidget extends BaseWidget< }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - tabs: VALIDATION_TYPES.TABS_DATA, - defaultTab: VALIDATION_TYPES.SELECTED_TAB, - }; - } onTabChange = (tabWidgetId: string) => { this.props.updateWidgetMetaProperty("selectedTabWidgetId", tabWidgetId, { diff --git a/app/client/src/widgets/TextWidget.tsx b/app/client/src/widgets/TextWidget.tsx index 90b8603064..0ccc039c68 100644 --- a/app/client/src/widgets/TextWidget.tsx +++ b/app/client/src/widgets/TextWidget.tsx @@ -3,10 +3,6 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType, TextSize } from "constants/WidgetConstants"; import TextComponent from "components/designSystems/blueprint/TextComponent"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import * as Sentry from "@sentry/react"; @@ -24,6 +20,7 @@ class TextWidget extends BaseWidget { placeholderText: "Enter text", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "shouldScroll", @@ -41,6 +38,7 @@ class TextWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -143,14 +141,6 @@ class TextWidget extends BaseWidget { }, ]; } - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - text: VALIDATION_TYPES.TEXT, - textStyle: VALIDATION_TYPES.TEXT, - shouldScroll: VALIDATION_TYPES.BOOLEAN, - }; - } getPageView() { return ( diff --git a/app/client/src/widgets/VideoWidget.tsx b/app/client/src/widgets/VideoWidget.tsx index 1f56fa0994..f0a1d0895c 100644 --- a/app/client/src/widgets/VideoWidget.tsx +++ b/app/client/src/widgets/VideoWidget.tsx @@ -3,10 +3,6 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "constants/WidgetConstants"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { VALIDATION_TYPES } from "constants/WidgetValidation"; -import { - WidgetPropertyValidationType, - BASE_WIDGET_VALIDATION, -} from "utils/WidgetValidation"; import Skeleton from "components/utils/Skeleton"; import * as Sentry from "@sentry/react"; import { retryPromise } from "utils/AppsmithUtils"; @@ -40,6 +36,7 @@ class VideoWidget extends BaseWidget { inputType: "TEXT", isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.TEXT, }, { propertyName: "autoPlay", @@ -49,6 +46,7 @@ class VideoWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, { helpText: "Controls the visibility of the widget", @@ -58,6 +56,7 @@ class VideoWidget extends BaseWidget { isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, + validation: VALIDATION_TYPES.BOOLEAN, }, ], }, @@ -96,12 +95,6 @@ class VideoWidget extends BaseWidget { ]; } private _player = React.createRef(); - static getPropertyValidationMap(): WidgetPropertyValidationType { - return { - ...BASE_WIDGET_VALIDATION, - url: VALIDATION_TYPES.TEXT, - }; - } static getMetaPropertiesMap(): Record { return { diff --git a/app/client/src/workers/DataTreeEvaluator.ts b/app/client/src/workers/DataTreeEvaluator.ts index f32995e889..1a724726de 100644 --- a/app/client/src/workers/DataTreeEvaluator.ts +++ b/app/client/src/workers/DataTreeEvaluator.ts @@ -24,6 +24,7 @@ import { CrashingError, DataTreeDiffEvent, getAllPaths, + getEntityNameAndPropertyPath, getImmediateParentsOfPropertyPaths, getValidatedTree, makeParentsDependOnChildren, @@ -86,7 +87,7 @@ export default class DataTreeEvaluator { const evaluateEnd = performance.now(); // Validate Widgets const validateStart = performance.now(); - this.evalTree = getValidatedTree(this.widgetConfigMap, evaluatedTree); + this.evalTree = getValidatedTree(evaluatedTree); const validateEnd = performance.now(); this.oldUnEvalTree = unEvalTree; @@ -350,24 +351,25 @@ export default class DataTreeEvaluator { const tree = _.cloneDeep(oldUnevalTree); try { return sortedDependencies.reduce( - (currentTree: DataTree, propertyPath: string) => { - this.logs.push(`evaluating ${propertyPath}`); - const entityName = propertyPath.split(".")[0]; + (currentTree: DataTree, fullPropertyPath: string) => { + this.logs.push(`evaluating ${fullPropertyPath}`); + const { entityName, propertyPath } = getEntityNameAndPropertyPath( + fullPropertyPath, + ); const entity: DataTreeEntity = currentTree[entityName]; - const unEvalPropertyValue = _.get(currentTree as any, propertyPath); + const unEvalPropertyValue = _.get( + currentTree as any, + fullPropertyPath, + ); const isABindingPath = (isAction(entity) || isWidget(entity)) && - isPathADynamicBinding( - entity, - propertyPath.substring(propertyPath.indexOf(".") + 1), - ); + isPathADynamicBinding(entity, propertyPath); let evalPropertyValue; const requiresEval = isABindingPath && isDynamicValue(unEvalPropertyValue); if (requiresEval) { try { evalPropertyValue = this.evaluateDynamicProperty( - propertyPath, currentTree, unEvalPropertyValue, ); @@ -376,7 +378,7 @@ export default class DataTreeEvaluator { type: EvalErrorTypes.EVAL_PROPERTY_ERROR, message: e.message, context: { - propertyPath, + propertyPath: fullPropertyPath, }, }); evalPropertyValue = undefined; @@ -386,42 +388,37 @@ export default class DataTreeEvaluator { } if (isWidget(entity)) { const widgetEntity = entity; - // TODO fix for nested properties - // For nested properties like Table1.selectedRow.email - // The following line will calculated the property name to be selectedRow - // instead of selectedRow.email - const propertyName = propertyPath.split(".")[1]; const defaultPropertyMap = this.widgetConfigMap[widgetEntity.type] .defaultProperties; const isDefaultProperty = !!Object.values( defaultPropertyMap, ).filter( - (defaultPropertyName) => propertyName === defaultPropertyName, + (defaultPropertyName) => propertyPath === defaultPropertyName, ).length; - if (propertyName) { + if (propertyPath) { let parsedValue = this.validateAndParseWidgetProperty( - propertyPath, + fullPropertyPath, widgetEntity, currentTree, evalPropertyValue, unEvalPropertyValue, isDefaultProperty, ); - const hasDefaultProperty = propertyName in defaultPropertyMap; + const hasDefaultProperty = propertyPath in defaultPropertyMap; if (hasDefaultProperty) { - const defaultProperty = defaultPropertyMap[propertyName]; + const defaultProperty = defaultPropertyMap[propertyPath]; parsedValue = this.overwriteDefaultDependentProps( defaultProperty, parsedValue, - propertyPath, + fullPropertyPath, widgetEntity, ); } - return _.set(currentTree, propertyPath, parsedValue); + return _.set(currentTree, fullPropertyPath, parsedValue); } - return _.set(currentTree, propertyPath, evalPropertyValue); + return _.set(currentTree, fullPropertyPath, evalPropertyValue); } else { - return _.set(currentTree, propertyPath, evalPropertyValue); + return _.set(currentTree, fullPropertyPath, evalPropertyValue); } }, tree, @@ -573,7 +570,6 @@ export default class DataTreeEvaluator { } evaluateDynamicProperty( - propertyPath: string, currentTree: DataTree, unEvalPropertyValue: any, ): any { @@ -581,14 +577,14 @@ export default class DataTreeEvaluator { } validateAndParseWidgetProperty( - propertyPath: string, + fullPropertyPath: string, widget: DataTreeWidget, currentTree: DataTree, evalPropertyValue: any, unEvalPropertyValue: string, isDefaultProperty: boolean, ): any { - const entityPropertyName = _.drop(propertyPath.split(".")).join("."); + const { propertyPath } = getEntityNameAndPropertyPath(fullPropertyPath); let valueToValidate = evalPropertyValue; if (isPathADynamicTrigger(widget, propertyPath)) { const { triggers } = this.getDynamicValue( @@ -599,12 +595,12 @@ export default class DataTreeEvaluator { ); valueToValidate = triggers; } + const validation = widget.validationPaths[propertyPath]; const { parsed, isValid, message, transformed } = validateWidgetProperty( - this.widgetConfigMap, - widget.type, - entityPropertyName, + propertyPath, valueToValidate, widget, + validation, currentTree, ); const evaluatedValue = isValid @@ -613,23 +609,23 @@ export default class DataTreeEvaluator { ? evalPropertyValue : transformed; const safeEvaluatedValue = removeFunctions(evaluatedValue); - _.set(widget, `evaluatedValues.${entityPropertyName}`, safeEvaluatedValue); + _.set(widget, `evaluatedValues.${propertyPath}`, safeEvaluatedValue); if (!isValid) { - _.set(widget, `invalidProps.${entityPropertyName}`, true); - _.set(widget, `validationMessages.${entityPropertyName}`, message); + _.set(widget, `invalidProps.${propertyPath}`, true); + _.set(widget, `validationMessages.${propertyPath}`, message); } else { - _.set(widget, `invalidProps.${entityPropertyName}`, false); - _.set(widget, `validationMessages.${entityPropertyName}`, ""); + _.set(widget, `invalidProps.${propertyPath}`, false); + _.set(widget, `validationMessages.${propertyPath}`, ""); } - if (isPathADynamicTrigger(widget, entityPropertyName)) { + if (isPathADynamicTrigger(widget, propertyPath)) { return unEvalPropertyValue; } else { - const parsedCache = this.getParsedValueCache(propertyPath); + const parsedCache = this.getParsedValueCache(fullPropertyPath); // In case this is a default property, always set the cache even if the value remains the same so that the version // in cache gets updated and the property dependent on default property updates accordingly. if (!equal(parsedCache.value, parsed) || isDefaultProperty) { - this.parsedValueCache.set(propertyPath, { + this.parsedValueCache.set(fullPropertyPath, { value: parsed, version: Date.now(), }); diff --git a/app/client/src/workers/evaluate.test.ts b/app/client/src/workers/evaluate.test.ts index c07c7a3a79..d658e35fc4 100644 --- a/app/client/src/workers/evaluate.test.ts +++ b/app/client/src/workers/evaluate.test.ts @@ -24,6 +24,7 @@ describe("evaluate", () => { ENTITY_TYPE: ENTITY_TYPE.WIDGET, bindingPaths: {}, triggerPaths: {}, + validationPaths: {}, }; const dataTree: DataTree = { Input1: widget, diff --git a/app/client/src/workers/evaluation.test.ts b/app/client/src/workers/evaluation.test.ts index 519d500603..bfda076995 100644 --- a/app/client/src/workers/evaluation.test.ts +++ b/app/client/src/workers/evaluation.test.ts @@ -7,27 +7,15 @@ import { WidgetTypeConfigMap } from "../utils/WidgetFactory"; import { RenderModes, WidgetTypes } from "../constants/WidgetConstants"; import { PluginType } from "../entities/Action"; import DataTreeEvaluator from "workers/DataTreeEvaluator"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { CONTAINER_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, TEXT_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - text: "TEXT", - textStyle: "TEXT", - shouldScroll: "BOOLEAN", - }, defaultProperties: {}, derivedProperties: { value: "{{ this.text }}", @@ -35,38 +23,11 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, BUTTON_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - text: "TEXT", - buttonStyle: "TEXT", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, INPUT_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - inputType: "TEXT", - defaultText: "TEXT", - text: "TEXT", - regex: "REGEX", - errorMessage: "TEXT", - placeholderText: "TEXT", - maxChars: "NUMBER", - minNum: "NUMBER", - maxNum: "NUMBER", - label: "TEXT", - inputValidators: "ARRAY", - focusIndex: "NUMBER", - isAutoFocusEnabled: "BOOLEAN", - isRequired: "BOOLEAN", - isValid: "BOOLEAN", - }, defaultProperties: { text: "defaultText", }, @@ -81,13 +42,6 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { }, }, CHECKBOX_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - label: "TEXT", - defaultCheckedState: "BOOLEAN", - }, defaultProperties: { isChecked: "defaultCheckedState", }, @@ -97,18 +51,6 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, DROP_DOWN_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - placeholderText: "TEXT", - label: "TEXT", - options: "OPTIONS_DATA", - selectionType: "TEXT", - isRequired: "BOOLEAN", - selectedOptionValues: "ARRAY", - defaultOptionValue: "DEFAULT_OPTION_VALUE", - }, defaultProperties: { selectedOptionValue: "defaultOptionValue", selectedOptionValueArr: "defaultOptionValue", @@ -131,16 +73,6 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, RADIO_GROUP_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - label: "TEXT", - options: "OPTIONS_DATA", - selectedOptionValue: "TEXT", - defaultOptionValue: "TEXT", - isRequired: "BOOLEAN", - }, defaultProperties: { selectedOptionValue: "defaultOptionValue", }, @@ -153,32 +85,11 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, IMAGE_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - image: "TEXT", - imageShape: "TEXT", - defaultImage: "TEXT", - maxZoomLevel: "NUMBER", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, TABLE_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - tableData: "TABLE_DATA", - nextPageKey: "TEXT", - prevPageKey: "TEXT", - label: "TEXT", - searchText: "TEXT", - defaultSearchText: "TEXT", - defaultSelectedRow: "DEFAULT_SELECTED_ROW", - }, defaultProperties: { searchText: "defaultSearchText", selectedRowIndex: "defaultSelectedRow", @@ -195,12 +106,6 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { }, }, VIDEO_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - url: "TEXT", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: { @@ -208,16 +113,6 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { }, }, FILE_PICKER_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - label: "TEXT", - maxNumFiles: "NUMBER", - allowedFileTypes: "ARRAY", - files: "ARRAY", - isRequired: "BOOLEAN", - }, defaultProperties: {}, derivedProperties: { isValid: "{{ this.isRequired ? this.files.length > 0 : true }}", @@ -229,20 +124,6 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { }, }, DATE_PICKER_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - defaultDate: "DATE", - timezone: "TEXT", - enableTimePicker: "BOOLEAN", - dateFormat: "TEXT", - label: "TEXT", - datePickerType: "TEXT", - maxDate: "DATE", - minDate: "DATE", - isRequired: "BOOLEAN", - }, defaultProperties: { selectedDate: "defaultDate", }, @@ -253,20 +134,6 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, DATE_PICKER_WIDGET2: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - defaultDate: "DATE", - timezone: "TEXT", - enableTimePicker: "BOOLEAN", - dateFormat: "TEXT", - label: "TEXT", - datePickerType: "TEXT", - maxDate: "DATE", - minDate: "DATE", - isRequired: "BOOLEAN", - }, defaultProperties: { selectedDate: "defaultDate", }, @@ -277,10 +144,6 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, TABS_WIDGET: { - validations: { - tabs: "TABS_DATA", - defaultTab: "SELECTED_TAB", - }, defaultProperties: {}, derivedProperties: { selectedTab: @@ -289,23 +152,11 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, MODAL_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, RICH_TEXT_EDITOR_WIDGET: { - validations: { - text: "TEXT", - placeholder: "TEXT", - defaultValue: "TEXT", - isDisabled: "BOOLEAN", - isVisible: "BOOLEAN", - }, defaultProperties: { text: "defaultText", }, @@ -315,52 +166,21 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, CHART_WIDGET: { - validations: { - xAxisName: "TEXT", - yAxisName: "TEXT", - chartName: "TEXT", - isVisible: "BOOLEAN", - chartData: "CHART_DATA", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, FORM_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, FORM_BUTTON_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - text: "TEXT", - disabledWhenInvalid: "BOOLEAN", - buttonStyle: "TEXT", - buttonType: "TEXT", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, MAP_WIDGET: { - validations: { - defaultMarkers: "MARKERS", - isDisabled: "BOOLEAN", - isVisible: "BOOLEAN", - enableSearch: "BOOLEAN", - enablePickLocation: "BOOLEAN", - allowZoom: "BOOLEAN", - zoomLevel: "NUMBER", - mapCenter: "OBJECT", - }, defaultProperties: { markers: "defaultMarkers", center: "mapCenter", @@ -369,31 +189,16 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = { metaProperties: {}, }, CANVAS_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, ICON_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, }, SKELETON_WIDGET: { - validations: { - isLoading: "BOOLEAN", - isVisible: "BOOLEAN", - isDisabled: "BOOLEAN", - }, defaultProperties: {}, derivedProperties: {}, metaProperties: {}, @@ -416,6 +221,7 @@ const BASE_WIDGET: DataTreeWidget = { version: 1, bindingPaths: {}, triggerPaths: {}, + validationPaths: {}, ENTITY_TYPE: ENTITY_TYPE.WIDGET, }; @@ -457,6 +263,9 @@ describe("DataTreeEvaluator", () => { bindingPaths: { text: true, }, + validationPaths: { + text: VALIDATION_TYPES.TEXT, + }, }, Text3: { ...BASE_WIDGET, @@ -467,6 +276,9 @@ describe("DataTreeEvaluator", () => { bindingPaths: { text: true, }, + validationPaths: { + text: VALIDATION_TYPES.TEXT, + }, }, Dropdown1: { ...BASE_WIDGET, @@ -506,6 +318,9 @@ describe("DataTreeEvaluator", () => { selectedRow: true, selectedRows: true, }, + validationPaths: { + tableData: VALIDATION_TYPES.TABLE_DATA, + }, }, Text4: { ...BASE_WIDGET, @@ -515,6 +330,9 @@ describe("DataTreeEvaluator", () => { bindingPaths: { text: true, }, + validationPaths: { + text: VALIDATION_TYPES.TEXT, + }, }, }; const evaluator = new DataTreeEvaluator(WIDGET_CONFIG_MAP); diff --git a/app/client/src/workers/evaluation.worker.ts b/app/client/src/workers/evaluation.worker.ts index 2c9d3c65c0..85a7399224 100644 --- a/app/client/src/workers/evaluation.worker.ts +++ b/app/client/src/workers/evaluation.worker.ts @@ -73,7 +73,7 @@ ctx.addEventListener( }); console.error(e); } - dataTree = getValidatedTree(widgetTypeConfigMap, unevalTree); + dataTree = getValidatedTree(unevalTree); dataTreeEvaluator = undefined; } return { @@ -148,21 +148,9 @@ ctx.addEventListener( return true; } case EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY: { - const { - widgetType, - widgetTypeConfigMap, - property, - value, - props, - } = requestData; + const { property, value, props, validation } = requestData; return removeFunctions( - validateWidgetProperty( - widgetTypeConfigMap, - widgetType, - property, - value, - props, - ), + validateWidgetProperty(property, value, props, validation), ); } default: { diff --git a/app/client/src/workers/evaluationUtils.ts b/app/client/src/workers/evaluationUtils.ts index c92b6edce1..76d706ddec 100644 --- a/app/client/src/workers/evaluationUtils.ts +++ b/app/client/src/workers/evaluationUtils.ts @@ -3,9 +3,7 @@ import { isChildPropertyPath, isDynamicValue, } from "utils/DynamicBindingUtils"; -import { WidgetType } from "constants/WidgetConstants"; import { WidgetProps } from "widgets/BaseWidget"; -import { WidgetTypeConfigMap } from "utils/WidgetFactory"; import { VALIDATORS } from "./validations"; import { Diff } from "deep-diff"; import { @@ -16,6 +14,7 @@ import { ENTITY_TYPE, } from "entities/DataTree/dataTreeFactory"; import _ from "lodash"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; // Dropdown1.options[1].value -> Dropdown1.options[1] // Dropdown1.options[1] -> Dropdown1.options @@ -61,6 +60,16 @@ function isInt(val: string | number): boolean { return !isNaN(parseInt(val)); } +// Removes the entity name from the property path +export function getEntityNameAndPropertyPath( + fullPath: string, +): { entityName: string; propertyPath: string } { + const indexOfFirstDot = fullPath.indexOf("."); + const entityName = fullPath.substring(0, indexOfFirstDot); + const propertyPath = fullPath.substring(fullPath.indexOf(".") + 1); + return { entityName, propertyPath }; +} + export const translateDiffEventToDataTreeDiffEvent = ( difference: Diff, ): DataTreeDiff => { @@ -244,73 +253,53 @@ export const getImmediateParentsOfPropertyPaths = ( }; export function validateWidgetProperty( - widgetConfigMap: WidgetTypeConfigMap, - widgetType: WidgetType, property: string, value: any, props: WidgetProps, + validation?: VALIDATION_TYPES, dataTree?: DataTree, ) { - const propertyValidationTypes = widgetConfigMap[widgetType].validations; - const validationTypeOrValidator = propertyValidationTypes[property]; - let validator; - - if (typeof validationTypeOrValidator === "function") { - validator = validationTypeOrValidator; - } else { - validator = VALIDATORS[validationTypeOrValidator]; - } - if (validator) { - return validator(value, props, dataTree); - } else { + if (!validation) { return { isValid: true, parsed: value }; } + const validator = VALIDATORS[validation]; + if (!validator) { + return { isValid: true, parsed: value }; + } + return validator(value, props, dataTree); } -export function getValidatedTree( - widgetConfigMap: WidgetTypeConfigMap, - tree: DataTree, -) { +export function getValidatedTree(tree: DataTree) { return Object.keys(tree).reduce((tree, entityKey: string) => { const entity = tree[entityKey] as DataTreeWidget; if (!isWidget(entity)) { return tree; } const parsedEntity = { ...entity }; - Object.keys(entity).forEach((property: string) => { - const validationProperties = widgetConfigMap[entity.type].validations; - - if (property in validationProperties) { - const value = _.get(entity, property); - // Pass it through parse - const { - parsed, - isValid, - message, - transformed, - } = validateWidgetProperty( - widgetConfigMap, - entity.type, - property, - value, - entity, - tree, - ); - parsedEntity[property] = parsed; - const evaluatedValue = isValid - ? parsed - : _.isUndefined(transformed) - ? value - : transformed; - const safeEvaluatedValue = removeFunctions(evaluatedValue); - _.set(parsedEntity, `evaluatedValues.${property}`, safeEvaluatedValue); - if (!isValid) { - _.set(parsedEntity, `invalidProps.${property}`, true); - _.set(parsedEntity, `validationMessages.${property}`, message); - } else { - _.set(parsedEntity, `invalidProps.${property}`, false); - _.set(parsedEntity, `validationMessages.${property}`, ""); - } + Object.entries(entity.validationPaths).forEach(([property, validation]) => { + const value = _.get(entity, property); + // Pass it through parse + const { parsed, isValid, message, transformed } = validateWidgetProperty( + property, + value, + entity, + validation, + tree, + ); + _.set(parsedEntity, property, parsed); + const evaluatedValue = isValid + ? parsed + : _.isUndefined(transformed) + ? value + : transformed; + const safeEvaluatedValue = removeFunctions(evaluatedValue); + _.set(parsedEntity, `evaluatedValues.${property}`, safeEvaluatedValue); + if (!isValid) { + _.set(parsedEntity, `invalidProps.${property}`, true); + _.set(parsedEntity, `validationMessages.${property}`, message); + } else { + _.set(parsedEntity, `invalidProps.${property}`, false); + _.set(parsedEntity, `validationMessages.${property}`, ""); } }); return { ...tree, [entityKey]: parsedEntity }; diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts index 9c5fcd05f2..68a9356dd8 100644 --- a/app/client/src/workers/validations.ts +++ b/app/client/src/workers/validations.ts @@ -2,7 +2,6 @@ import { ISO_DATE_FORMAT, VALIDATION_TYPES, ValidationResponse, - ValidationType, Validator, } from "../constants/WidgetValidation"; import { DataTree } from "../entities/DataTree/dataTreeFactory"; @@ -48,7 +47,7 @@ export function validateDateString( const WIDGET_TYPE_VALIDATION_ERROR = "Value does not match type"; // TODO: Lot's of changes in validations.ts file -export const VALIDATORS: Record = { +export const VALIDATORS: Record = { [VALIDATION_TYPES.TEXT]: (value: any): ValidationResponse => { let parsed = value; if (isUndefined(value) || value === null) { diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 4a3012d16b..1e108aa510 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -3504,11 +3504,6 @@ dependencies: "@types/tern" "*" -"@types/component-emitter@^1.2.10": - version "1.2.10" - resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" - integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== - "@types/cookie@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" @@ -6920,7 +6915,7 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" -debug@^4.3.0, debug@~4.3.1: +debug@^4.3.0: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -8197,6 +8192,7 @@ fd-slicer@~1.1.0: figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== figures@^1.7.0: version "1.7.0" @@ -11106,7 +11102,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@4.x: +lodash@4.x, lodash@^4.6.1: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -11115,11 +11111,6 @@ lodash@4.x: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" -lodash@^4.6.1: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - log-symbols@3.0.0, log-symbols@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" @@ -15666,8 +15657,9 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + version "6.0.2" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" + integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== dependencies: figgy-pudding "^3.5.1" diff --git a/app/server/README.md b/app/server/README.md index ae3296205b..d59286da54 100644 --- a/app/server/README.md +++ b/app/server/README.md @@ -30,4 +30,4 @@ In order to test the code, you can run the following command: mvn -B clean package ``` -Please make sure that you have a local Redis instance running for the test cases. During tests, the MongoDB is run in-memory. So you don't require to be running a local MongoDB instance. +Please make sure that you have a local Redis instance running for the test cases. During tests, the MongoDB is run in-memory. So you don't require to be running a local MongoDB instance. diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/documenttype/DocumentTypeMapper.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/documenttype/DocumentTypeMapper.java index 2e1cfa0240..1c3f11f039 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/documenttype/DocumentTypeMapper.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/documenttype/DocumentTypeMapper.java @@ -58,8 +58,9 @@ public class DocumentTypeMapper implements TypeInformationMapper { @Override public TypeInformation resolveTypeFrom(Alias alias) { - if (aliasToTypeMap.containsKey((String) alias.getValue())) { - return aliasToTypeMap.get(alias.getValue()); + final String aliasAsString = alias.mapTyped(String.class); + if (aliasAsString != null && aliasToTypeMap.containsKey(aliasAsString)) { + return aliasToTypeMap.get(aliasAsString); } return null; } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/encryption/EncryptionHandler.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/encryption/EncryptionHandler.java index b2d9225618..c6230b2b12 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/encryption/EncryptionHandler.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/annotations/encryption/EncryptionHandler.java @@ -38,6 +38,9 @@ public class EncryptionHandler { // At this point source class represents the true polymorphic type of the document Class sourceClass = source.getClass(); + // Lock a thread wanting to find information about the same type + // So that this information retrieval is only done once + // Ignore this warning, this class reference will be on the heap List candidateFields = this.encryptedFieldsMap.get(sourceClass); if (candidateFields != null) { @@ -192,6 +195,7 @@ public class EncryptionHandler { encryptedFieldsMap.put(sourceClass, finalCandidateFields); return finalCandidateFields; + } boolean convertEncryption(Object source, Function transformer) { @@ -243,7 +247,6 @@ public class EncryptionHandler { // unknown types as polymorphic candidates over time // If that is the case then do we need to store the candidate field type by // known, unknown or polymorphic types at all? - // TODO Discuss w/ reviewer } } else { final Type[] typeNames = ((ParameterizedType) field.getGenericType()).getActualTypeArguments(); diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Property.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Property.java index 8263a4b72c..171dad363a 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Property.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Property.java @@ -16,14 +16,14 @@ public class Property { /* * A convenience constructor to create a Property object with just a key and a value. */ - public Property(String key, String value) { + public Property(String key, Object value) { this.key = key; this.value = value; } String key; - String value; + Object value; Boolean editable; diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java index cfe2641971..1084a9d4c0 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java @@ -315,7 +315,7 @@ public class AmazonS3Plugin extends BasePlugin { } - AmazonS3Action s3Action = AmazonS3Action.valueOf(properties.get(ACTION_PROPERTY_INDEX).getValue()); + AmazonS3Action s3Action = AmazonS3Action.valueOf((String) properties.get(ACTION_PROPERTY_INDEX).getValue()); query[0] = s3Action.name(); if (properties.size() < (1 + BUCKET_NAME_PROPERTY_INDEX) @@ -329,7 +329,7 @@ public class AmazonS3Plugin extends BasePlugin { ); } - final String bucketName = properties.get(BUCKET_NAME_PROPERTY_INDEX).getValue(); + final String bucketName = (String) properties.get(BUCKET_NAME_PROPERTY_INDEX).getValue(); requestProperties.put("bucket", bucketName == null ? "" : bucketName); if (StringUtils.isEmpty(bucketName)) { @@ -378,7 +378,7 @@ public class AmazonS3Plugin extends BasePlugin { if (properties.size() > PREFIX_PROPERTY_INDEX && properties.get(PREFIX_PROPERTY_INDEX) != null && properties.get(PREFIX_PROPERTY_INDEX).getValue() != null) { - prefix = properties.get(PREFIX_PROPERTY_INDEX).getValue(); + prefix = (String) properties.get(PREFIX_PROPERTY_INDEX).getValue(); } ArrayList listOfFiles = listAllFilesInBucket(connection, bucketName, prefix); @@ -390,13 +390,13 @@ public class AmazonS3Plugin extends BasePlugin { int durationInMinutes; if (properties.size() < (1 + URL_EXPIRY_DURATION_PROPERTY_INDEX) || properties.get(URL_EXPIRY_DURATION_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(URL_EXPIRY_DURATION_PROPERTY_INDEX).getValue())) { + || StringUtils.isEmpty((String) properties.get(URL_EXPIRY_DURATION_PROPERTY_INDEX).getValue())) { durationInMinutes = DEFAULT_URL_EXPIRY_IN_MINUTES; } else { try { durationInMinutes = Integer .parseInt( - properties + (String) properties .get(URL_EXPIRY_DURATION_PROPERTY_INDEX) .getValue() ); @@ -451,13 +451,13 @@ public class AmazonS3Plugin extends BasePlugin { int durationInMinutes; if (properties.size() < (1 + URL_EXPIRY_DURATION_FOR_UPLOAD_PROPERTY_INDEX) || properties.get(URL_EXPIRY_DURATION_FOR_UPLOAD_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(URL_EXPIRY_DURATION_FOR_UPLOAD_PROPERTY_INDEX).getValue())) { + || StringUtils.isEmpty((String) properties.get(URL_EXPIRY_DURATION_FOR_UPLOAD_PROPERTY_INDEX).getValue())) { durationInMinutes = DEFAULT_URL_EXPIRY_IN_MINUTES; } else { try { durationInMinutes = Integer .parseInt( - properties + (String) properties .get(URL_EXPIRY_DURATION_FOR_UPLOAD_PROPERTY_INDEX) .getValue() ); @@ -591,7 +591,7 @@ public class AmazonS3Plugin extends BasePlugin { */ if (properties == null || properties.get(S3_SERVICE_PROVIDER_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(S3_SERVICE_PROVIDER_PROPERTY_INDEX).getValue())) { + || StringUtils.isEmpty((String) properties.get(S3_SERVICE_PROVIDER_PROPERTY_INDEX).getValue())) { return Mono.error( new AppsmithPluginException( AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, @@ -607,7 +607,7 @@ public class AmazonS3Plugin extends BasePlugin { if (!usingCustomEndpoint && (properties.size() < (AWS_S3_REGION_PROPERTY_INDEX + 1) || properties.get(AWS_S3_REGION_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(AWS_S3_REGION_PROPERTY_INDEX).getValue()))) { + || StringUtils.isEmpty((String) properties.get(AWS_S3_REGION_PROPERTY_INDEX).getValue()))) { return Mono.error( new AppsmithPluginException( AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, @@ -636,7 +636,7 @@ public class AmazonS3Plugin extends BasePlugin { if (usingCustomEndpoint && (properties.size() < (CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX + 1) || properties.get(CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX).getValue()))) { + || StringUtils.isEmpty((String) properties.get(CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX).getValue()))) { return Mono.error( new AppsmithPluginException( AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, @@ -647,9 +647,9 @@ public class AmazonS3Plugin extends BasePlugin { ); } - final String region = usingCustomEndpoint ? - properties.get(CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX).getValue() : - properties.get(AWS_S3_REGION_PROPERTY_INDEX).getValue(); + final String region = (String) (usingCustomEndpoint ? + properties.get(CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX).getValue() : + properties.get(AWS_S3_REGION_PROPERTY_INDEX).getValue()); DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); if (authentication == null @@ -776,7 +776,7 @@ public class AmazonS3Plugin extends BasePlugin { */ if (properties == null || properties.get(S3_SERVICE_PROVIDER_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(S3_SERVICE_PROVIDER_PROPERTY_INDEX).getValue())) { + || StringUtils.isEmpty((String) properties.get(S3_SERVICE_PROVIDER_PROPERTY_INDEX).getValue())) { invalids.add("Appsmith has failed to fetch the 'S3 Service Provider' field properties. Please " + "reach out to Appsmith customer support to resolve this."); } @@ -786,7 +786,7 @@ public class AmazonS3Plugin extends BasePlugin { if (!usingCustomEndpoint && (properties.size() < (AWS_S3_REGION_PROPERTY_INDEX + 1) || properties.get(AWS_S3_REGION_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(AWS_S3_REGION_PROPERTY_INDEX).getValue()))) { + || StringUtils.isEmpty((String) properties.get(AWS_S3_REGION_PROPERTY_INDEX).getValue()))) { invalids.add("Required parameter 'Region' is empty. Did you forget to edit the 'Region' field" + " in the datasource creation form ? You need to fill it with the region where " + "your AWS S3 instance is hosted."); @@ -805,7 +805,7 @@ public class AmazonS3Plugin extends BasePlugin { if (usingCustomEndpoint && (properties.size() < (CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX + 1) || properties.get(CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX).getValue()))) { + || StringUtils.isEmpty((String) properties.get(CUSTOM_ENDPOINT_REGION_PROPERTY_INDEX).getValue()))) { invalids.add("Required parameter 'Region' is empty. Did you forget to edit the 'Region' field" + " in the datasource creation form ? You need to fill it with the region where " + "your S3 instance is hosted."); diff --git a/app/server/appsmith-plugins/elasticSearchPlugin/src/main/java/com/external/plugins/ElasticSearchPlugin.java b/app/server/appsmith-plugins/elasticSearchPlugin/src/main/java/com/external/plugins/ElasticSearchPlugin.java index fe37bf526f..f01586f4db 100644 --- a/app/server/appsmith-plugins/elasticSearchPlugin/src/main/java/com/external/plugins/ElasticSearchPlugin.java +++ b/app/server/appsmith-plugins/elasticSearchPlugin/src/main/java/com/external/plugins/ElasticSearchPlugin.java @@ -187,7 +187,7 @@ public class ElasticSearchPlugin extends BasePlugin { clientBuilder.setDefaultHeaders( (Header[]) datasourceConfiguration.getHeaders() .stream() - .map(h -> new BasicHeader(h.getKey(), h.getValue())) + .map(h -> new BasicHeader(h.getKey(), (String) h.getValue())) .toArray() ); } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java index a585bdfc9f..21fd6d0a0d 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java @@ -43,7 +43,6 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -114,7 +113,7 @@ public class FirestorePlugin extends BasePlugin { final List properties = actionConfiguration.getPluginSpecifiedTemplates(); final com.external.plugins.Method method = CollectionUtils.isEmpty(properties) ? null - : com.external.plugins.Method.valueOf(properties.get(0).getValue()); + : com.external.plugins.Method.valueOf((String) properties.get(0).getValue()); requestData.put("method", method == null ? "" : method.toString()); final PaginationField paginationField = executeActionDTO == null ? null : executeActionDTO.getPaginationField(); @@ -189,10 +188,10 @@ public class FirestorePlugin extends BasePlugin { || Method.CREATE_DOCUMENT.equals(method) || Method.ADD_TO_COLLECTION.equals(method)) && (properties == null || ((properties.size() < FIELDVALUE_TIMESTAMP_PROPERTY_INDEX + 1 || properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX).getValue())) + || StringUtils.isEmpty((String) properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX).getValue())) && (properties.size() < FIELDVALUE_DELETE_PROPERTY_INDEX || properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX) == null - || StringUtils.isEmpty(properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX).getValue()))))) { + || StringUtils.isEmpty((String) properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX).getValue()))))) { return Mono.error(new AppsmithPluginException( AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "The method " + method.toString() + " needs at least one of the following " + @@ -250,7 +249,7 @@ public class FirestorePlugin extends BasePlugin { if(!Method.UPDATE_DOCUMENT.equals(method) && properties.size() > FIELDVALUE_DELETE_PROPERTY_INDEX && properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX) != null - && !StringUtils.isEmpty(properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX).getValue())) { + && !StringUtils.isEmpty((String) properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX).getValue())) { throw new AppsmithPluginException( AppsmithPluginError.PLUGIN_ERROR, "Appsmith has found an unexpected query form property - 'Delete Key Value Pair Path'. Please " + @@ -263,8 +262,8 @@ public class FirestorePlugin extends BasePlugin { */ if( properties.size() > FIELDVALUE_DELETE_PROPERTY_INDEX && properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX) != null - && !StringUtils.isEmpty(properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX).getValue())) { - String deletePaths = properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX).getValue(); + && !StringUtils.isEmpty((String) properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX).getValue())) { + String deletePaths = (String) properties.get(FIELDVALUE_DELETE_PROPERTY_INDEX).getValue(); List deletePathsList; try { deletePathsList = objectMapper.readValue(deletePaths, new TypeReference>(){}); @@ -297,7 +296,7 @@ public class FirestorePlugin extends BasePlugin { || Method.DELETE_DOCUMENT.equals(method)) && properties.size() > FIELDVALUE_TIMESTAMP_PROPERTY_INDEX && properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX) != null - && !StringUtils.isEmpty(properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX).getValue())) { + && !StringUtils.isEmpty((String) properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX).getValue())) { throw new AppsmithPluginException( AppsmithPluginError.PLUGIN_ERROR, "Appsmith has found an unexpected query form property - 'Timestamp Value Path'. Please reach " + @@ -310,8 +309,8 @@ public class FirestorePlugin extends BasePlugin { */ if(properties.size() > FIELDVALUE_TIMESTAMP_PROPERTY_INDEX && properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX) != null - && !StringUtils.isEmpty(properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX).getValue())) { - String timestampValuePaths = properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX).getValue(); + && !StringUtils.isEmpty((String) properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX).getValue())) { + String timestampValuePaths = (String) properties.get(FIELDVALUE_TIMESTAMP_PROPERTY_INDEX).getValue(); List timestampPathsStringList; // ["key1.key2", "key3.key4"] try { timestampPathsStringList = objectMapper.readValue(timestampValuePaths, @@ -511,7 +510,7 @@ public class FirestorePlugin extends BasePlugin { return defaultValue; } - final String value = property.getValue(); + final String value = (String) property.getValue(); return value != null ? value : defaultValue; } diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java index 079deca7d7..dc8dd131e0 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java @@ -129,7 +129,14 @@ public class MongoPlugin extends BasePlugin { // Since properties is not empty, we are guaranteed to find the first property. } else if (properties.get(SMART_BSON_SUBSTITUTION_INDEX) != null){ - smartBsonSubstitution = Boolean.parseBoolean(properties.get(SMART_BSON_SUBSTITUTION_INDEX).getValue()); + Object ssubValue = properties.get(SMART_BSON_SUBSTITUTION_INDEX).getValue(); + if (ssubValue instanceof Boolean) { + smartBsonSubstitution = (Boolean) ssubValue; + } else if (ssubValue instanceof String) { + smartBsonSubstitution = Boolean.parseBoolean((String) ssubValue); + } else { + smartBsonSubstitution = false; + } } else { smartBsonSubstitution = false; } diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/resources/dependency.json b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/dependency.json new file mode 100644 index 0000000000..007639f0f9 --- /dev/null +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/dependency.json @@ -0,0 +1,5 @@ +{ + "dependencies" : { + "actionConfiguration.body" : ["actionConfiguration.pluginSpecifiedTemplates[0].value"] + } +} \ No newline at end of file diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/resources/editor.json b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/editor.json index 838f2bcdf2..3f64288940 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/resources/editor.json +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/editor.json @@ -11,8 +11,11 @@ "evaluationSubstitutionType": "SMART_SUBSTITUTE", "hidden": { "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", - "comparison": "EQUALS", - "value": "false" + "comparison": "IN", + "value": [ + "false", + false + ] } }, { @@ -22,8 +25,11 @@ "evaluationSubstitutionType": "TEMPLATE", "hidden": { "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", - "comparison": "EQUALS", - "value": "true" + "comparison": "IN", + "value": [ + "true", + true + ] } } ] diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/resources/setting.json b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/setting.json index 3bb2c600f4..a155ed421b 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/resources/setting.json +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/setting.json @@ -21,7 +21,7 @@ "info": "Turning on this property fixes the BSON substitution of bindings in the Mongo BSON document by adding/removing quotes intelligently and reduces developer errors", "configProperty": "actionConfiguration.pluginSpecifiedTemplates[0].value", "controlType": "SWITCH", - "initialValue": "false" + "initialValue": false }, { "label": "Query timeout (in milliseconds)", diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java b/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java index 5c2d9fa0ba..110b18f989 100644 --- a/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java +++ b/app/server/appsmith-plugins/mssqlPlugin/src/main/java/com/external/plugins/MssqlPlugin.java @@ -123,7 +123,14 @@ public class MssqlPlugin extends BasePlugin { */ isPreparedStatement = false; } else if (properties.get(PREPARED_STATEMENT_INDEX) != null){ - isPreparedStatement = Boolean.parseBoolean(properties.get(PREPARED_STATEMENT_INDEX).getValue()); + Object psValue = properties.get(PREPARED_STATEMENT_INDEX).getValue(); + if (psValue instanceof Boolean) { + isPreparedStatement = (Boolean) psValue; + } else if (psValue instanceof String) { + isPreparedStatement = Boolean.parseBoolean((String) psValue); + } else { + isPreparedStatement = false; + } } else { isPreparedStatement = false; } diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/dependency.json b/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/dependency.json new file mode 100644 index 0000000000..eb5f3a769e --- /dev/null +++ b/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/dependency.json @@ -0,0 +1,5 @@ +{ + "dependencies" : { + "actionConfiguration.body" : ["actionConfiguration.pluginSpecifiedTemplates[0].value"] + } +} \ No newline at end of file diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/editor.json b/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/editor.json index d5d37ad79a..efc9c38e6e 100644 --- a/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/editor.json +++ b/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/editor.json @@ -11,8 +11,11 @@ "evaluationSubstitutionType": "PARAMETER", "hidden": { "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", - "comparison": "EQUALS", - "value": "false" + "comparison": "IN", + "value": [ + "false", + false + ] } }, { @@ -22,8 +25,11 @@ "evaluationSubstitutionType": "TEMPLATE", "hidden": { "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", - "comparison": "EQUALS", - "value": "true" + "comparison": "IN", + "value": [ + "true", + true + ] } } ] diff --git a/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/setting.json b/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/setting.json index 111caa1249..4c433b6bc8 100644 --- a/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/setting.json +++ b/app/server/appsmith-plugins/mssqlPlugin/src/main/resources/setting.json @@ -21,7 +21,7 @@ "info": "Turning on Prepared Statement makes the query parametrized. This in turn makes it resilient against SQL injections", "configProperty": "actionConfiguration.pluginSpecifiedTemplates[0].value", "controlType": "SWITCH", - "initialValue": "false" + "initialValue": false }, { "label": "Query timeout (in milliseconds)", diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java index 784b243f00..6bd4f7bbf2 100644 --- a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java @@ -166,7 +166,14 @@ public class MySqlPlugin extends BasePlugin { */ isPreparedStatement = false; } else if (properties.get(PREPARED_STATEMENT_INDEX) != null){ - isPreparedStatement = Boolean.parseBoolean(properties.get(PREPARED_STATEMENT_INDEX).getValue()); + Object psValue = properties.get(PREPARED_STATEMENT_INDEX).getValue(); + if (psValue instanceof Boolean) { + isPreparedStatement = (Boolean) psValue; + } else if (psValue instanceof String) { + isPreparedStatement = Boolean.parseBoolean((String) psValue); + } else { + isPreparedStatement = false; + } } else { isPreparedStatement = false; } diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/dependency.json b/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/dependency.json new file mode 100644 index 0000000000..eb5f3a769e --- /dev/null +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/dependency.json @@ -0,0 +1,5 @@ +{ + "dependencies" : { + "actionConfiguration.body" : ["actionConfiguration.pluginSpecifiedTemplates[0].value"] + } +} \ No newline at end of file diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/editor.json b/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/editor.json index d5d37ad79a..efc9c38e6e 100644 --- a/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/editor.json +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/editor.json @@ -11,8 +11,11 @@ "evaluationSubstitutionType": "PARAMETER", "hidden": { "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", - "comparison": "EQUALS", - "value": "false" + "comparison": "IN", + "value": [ + "false", + false + ] } }, { @@ -22,8 +25,11 @@ "evaluationSubstitutionType": "TEMPLATE", "hidden": { "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", - "comparison": "EQUALS", - "value": "true" + "comparison": "IN", + "value": [ + "true", + true + ] } } ] diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/setting.json b/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/setting.json index 111caa1249..4c433b6bc8 100644 --- a/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/setting.json +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/setting.json @@ -21,7 +21,7 @@ "info": "Turning on Prepared Statement makes the query parametrized. This in turn makes it resilient against SQL injections", "configProperty": "actionConfiguration.pluginSpecifiedTemplates[0].value", "controlType": "SWITCH", - "initialValue": "false" + "initialValue": false }, { "label": "Query timeout (in milliseconds)", diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java index 40deb74009..a89dbdfe57 100644 --- a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java @@ -172,7 +172,14 @@ public class PostgresPlugin extends BasePlugin { */ isPreparedStatement = false; } else if (properties.get(PREPARED_STATEMENT_INDEX) != null){ - isPreparedStatement = Boolean.parseBoolean(properties.get(PREPARED_STATEMENT_INDEX).getValue()); + Object psValue = properties.get(PREPARED_STATEMENT_INDEX).getValue(); + if (psValue instanceof Boolean) { + isPreparedStatement = (Boolean) psValue; + } else if (psValue instanceof String) { + isPreparedStatement = Boolean.parseBoolean((String) psValue); + } else { + isPreparedStatement = false; + } } else { isPreparedStatement = false; } diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/resources/dependency.json b/app/server/appsmith-plugins/postgresPlugin/src/main/resources/dependency.json new file mode 100644 index 0000000000..eb5f3a769e --- /dev/null +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/resources/dependency.json @@ -0,0 +1,5 @@ +{ + "dependencies" : { + "actionConfiguration.body" : ["actionConfiguration.pluginSpecifiedTemplates[0].value"] + } +} \ No newline at end of file diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/resources/editor.json b/app/server/appsmith-plugins/postgresPlugin/src/main/resources/editor.json index 20aa59614b..4f538f0836 100644 --- a/app/server/appsmith-plugins/postgresPlugin/src/main/resources/editor.json +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/resources/editor.json @@ -11,8 +11,11 @@ "evaluationSubstitutionType": "PARAMETER", "hidden": { "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", - "comparison": "EQUALS", - "value": "false" + "comparison": "IN", + "value": [ + "false", + false + ] } }, { @@ -22,8 +25,11 @@ "evaluationSubstitutionType": "TEMPLATE", "hidden": { "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", - "comparison": "EQUALS", - "value": "true" + "comparison": "IN", + "value": [ + "true", + true + ] } } ] diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/resources/setting.json b/app/server/appsmith-plugins/postgresPlugin/src/main/resources/setting.json index 111caa1249..4c433b6bc8 100644 --- a/app/server/appsmith-plugins/postgresPlugin/src/main/resources/setting.json +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/resources/setting.json @@ -21,7 +21,7 @@ "info": "Turning on Prepared Statement makes the query parametrized. This in turn makes it resilient against SQL injections", "configProperty": "actionConfiguration.pluginSpecifiedTemplates[0].value", "controlType": "SWITCH", - "initialValue": "false" + "initialValue": false }, { "label": "Query timeout (in milliseconds)", diff --git a/app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java b/app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java index 0dad825d0b..bcad096d7e 100644 --- a/app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java +++ b/app/server/appsmith-plugins/rapidApiPlugin/src/main/java/com/external/plugins/RapidApiPlugin.java @@ -94,11 +94,11 @@ public class RapidApiPlugin extends BasePlugin { for (Property property : actionConfiguration.getRouteParameters()) { // If either the key or the value is empty, skip if (property.getKey() != null && !property.getKey().isEmpty() && - property.getValue() != null && !property.getValue().isEmpty()) { + property.getValue() != null && !((String) property.getValue()).isEmpty()) { Pattern pattern = Pattern.compile("\\{" + property.getKey() + "\\}"); Matcher matcher = pattern.matcher(url); - url = matcher.replaceAll(URLEncoder.encode(property.getValue())); + url = matcher.replaceAll(URLEncoder.encode((String) property.getValue())); } } } @@ -126,11 +126,11 @@ public class RapidApiPlugin extends BasePlugin { if (property.getValue() != null) { if (!property.getType().equals(JSON_TYPE)) { - keyValueMap.put(property.getKey(), property.getValue()); + keyValueMap.put(property.getKey(), (String) property.getValue()); } else { // This is actually supposed to be the body and should not be in key-value format. No need to // convert the same. - jsonString = property.getValue(); + jsonString = (String) property.getValue(); break; } } @@ -285,7 +285,7 @@ public class RapidApiPlugin extends BasePlugin { private void addHeadersToRequest(WebClient.Builder webClientBuilder, List headers) { for (Property header : headers) { if (header.getKey() != null && !header.getKey().isEmpty()) { - webClientBuilder.defaultHeader(header.getKey(), header.getValue()); + webClientBuilder.defaultHeader(header.getKey(), (String) header.getValue()); } } } @@ -305,8 +305,8 @@ public class RapidApiPlugin extends BasePlugin { for (Property queryParam : queryParams) { // If either the key or the value is empty, skip if (queryParam.getKey() != null && !queryParam.getKey().isEmpty() && - queryParam.getValue() != null && !queryParam.getValue().isEmpty()) { - uriBuilder.queryParam(queryParam.getKey(), URLEncoder.encode(queryParam.getValue(), + queryParam.getValue() != null && !((String) queryParam.getValue()).isEmpty()) { + uriBuilder.queryParam(queryParam.getKey(), URLEncoder.encode((String) queryParam.getValue(), StandardCharsets.UTF_8)); } } diff --git a/app/server/appsmith-plugins/redshiftPlugin/pom.xml b/app/server/appsmith-plugins/redshiftPlugin/pom.xml index a1c5236e59..5562ebeca8 100644 --- a/app/server/appsmith-plugins/redshiftPlugin/pom.xml +++ b/app/server/appsmith-plugins/redshiftPlugin/pom.xml @@ -55,7 +55,7 @@ com.amazon.redshift redshift-jdbc42 - 2.0.0.1 + 2.0.0.4 runtime diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java index 24b56d3336..bb36d87c18 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java @@ -135,7 +135,14 @@ public class RestApiPlugin extends BasePlugin { // Since properties is not empty, we are guaranteed to find the first property. } else if (properties.get(SMART_JSON_SUBSTITUTION_INDEX) != null){ - smartJsonSubstitution = Boolean.parseBoolean(properties.get(SMART_JSON_SUBSTITUTION_INDEX).getValue()); + Object ssubValue = properties.get(SMART_JSON_SUBSTITUTION_INDEX).getValue(); + if (ssubValue instanceof Boolean) { + smartJsonSubstitution = (Boolean) ssubValue; + } else if (ssubValue instanceof String) { + smartJsonSubstitution = Boolean.parseBoolean((String) ssubValue); + } else { + smartJsonSubstitution = false; + } } else { smartJsonSubstitution = false; } @@ -400,7 +407,7 @@ public class RestApiPlugin extends BasePlugin { if (IS_SEND_SESSION_ENABLED_KEY.equals(property.getKey())) { isSendSessionEnabled = "Y".equals(property.getValue()); } else if (SESSION_SIGNATURE_KEY_KEY.equals(property.getKey())) { - secretKey = property.getValue(); + secretKey = (String) property.getValue(); } } @@ -429,7 +436,7 @@ public class RestApiPlugin extends BasePlugin { String reqBody = bodyFormData.stream() .map(property -> { String key = property.getKey(); - String value = property.getValue(); + String value = (String) property.getValue(); if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(reqContentType) && encodeParamsToggle == true) { @@ -462,7 +469,7 @@ public class RestApiPlugin extends BasePlugin { for (Property header : headers) { if (header.getKey().equalsIgnoreCase(HttpHeaders.CONTENT_TYPE)) { try { - MediaType.valueOf(header.getValue()); + MediaType.valueOf((String) header.getValue()); } catch (InvalidMediaTypeException e) { return e.getMessage(); } @@ -609,7 +616,7 @@ public class RestApiPlugin extends BasePlugin { if ("isSendSessionEnabled".equals(property.getKey())) { isSendSessionEnabled = "Y".equals(property.getValue()); } else if ("sessionSignatureKey".equals(property.getKey())) { - secretKey = property.getValue(); + secretKey = (String) property.getValue(); } } @@ -647,7 +654,7 @@ public class RestApiPlugin extends BasePlugin { for (Property header : headers) { String key = header.getKey(); if (StringUtils.isNotEmpty(key)) { - String value = header.getValue(); + String value = (String) header.getValue(); webClientBuilder.defaultHeader(key, value); if (HttpHeaders.CONTENT_TYPE.equalsIgnoreCase(key)) { @@ -678,7 +685,7 @@ public class RestApiPlugin extends BasePlugin { if (encodeParamsToggle == true) { uriBuilder.queryParam( URLEncoder.encode(key, StandardCharsets.UTF_8), - URLEncoder.encode(queryParam.getValue(), StandardCharsets.UTF_8) + URLEncoder.encode((String) queryParam.getValue(), StandardCharsets.UTF_8) ); } else { uriBuilder.queryParam( @@ -708,7 +715,7 @@ public class RestApiPlugin extends BasePlugin { MultiValueMap reqMultiMap = CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)); actionConfiguration.getHeaders().stream() - .forEach(header -> reqMultiMap.put(header.getKey(), Arrays.asList(header.getValue()))); + .forEach(header -> reqMultiMap.put(header.getKey(), Arrays.asList((String) header.getValue()))); actionExecutionRequest.setHeaders(objectMapper.valueToTree(reqMultiMap)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java index be6ff0b7a2..16d9af3e2d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java @@ -79,9 +79,9 @@ public class MongoConfig { return converter; } - @Bean - public EncryptionMongoEventListener encryptionMongoEventListener(EncryptionService encryptionService) { - return new EncryptionMongoEventListener(encryptionService); - } +// @Bean +// public EncryptionMongoEventListener encryptionMongoEventListener(EncryptionService encryptionService) { +// return new EncryptionMongoEventListener(encryptionService); +// } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java index a133819711..04e894de35 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java @@ -1,13 +1,13 @@ package com.appsmith.server.controllers; +import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.server.constants.Url; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; import com.appsmith.server.dtos.ActionViewDTO; -import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.server.dtos.LayoutDTO; -import com.appsmith.server.dtos.RefactorNameDTO; +import com.appsmith.server.dtos.RefactorActionNameDTO; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.services.ActionCollectionService; import com.appsmith.server.services.LayoutActionService; @@ -82,8 +82,8 @@ public class ActionController { } @PutMapping("/refactor") - public Mono> refactorActionName(@RequestBody RefactorNameDTO refactorNameDTO) { - return layoutActionService.refactorActionName(refactorNameDTO) + public Mono> refactorActionName(@RequestBody RefactorActionNameDTO refactorActionNameDTO) { + return layoutActionService.refactorActionName(refactorActionNameDTO) .map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/RefactorActionNameDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/RefactorActionNameDTO.java new file mode 100644 index 0000000000..b6d0c97a5d --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/RefactorActionNameDTO.java @@ -0,0 +1,14 @@ +package com.appsmith.server.dtos; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RefactorActionNameDTO { + String actionId; + String pageId; + String layoutId; + String oldName; + String newName; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java index 7b43b0ad70..f1c600af1c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java @@ -12,6 +12,7 @@ public enum AppsmithError { PLUGIN_ID_NOT_GIVEN(400, 4002, "Missing plugin id. Please enter one.", AppsmithErrorAction.DEFAULT, null), DATASOURCE_NOT_GIVEN(400, 4003, "Missing datasource. Add/enter/connect a datasource to create a valid action.", AppsmithErrorAction.DEFAULT, null), PAGE_ID_NOT_GIVEN(400, 4004, "Missing page id. Please enter one.", AppsmithErrorAction.DEFAULT, null), + DUPLICATE_KEY_USER_ERROR(400, 4005, "{0} already exists. Please use a different {1}", AppsmithErrorAction.DEFAULT, null), PAGE_DOESNT_BELONG_TO_USER_ORGANIZATION(400, 4006, "Page {0} does not belong to the current user {1} " + "organization", AppsmithErrorAction.LOG_EXTERNALLY, null), UNSUPPORTED_OPERATION(400, 4007, "Unsupported operation", AppsmithErrorAction.DEFAULT, null), @@ -81,9 +82,10 @@ public enum AppsmithError { FAIL_UPDATE_USER_IN_SESSION(500, 5008, "Unable to update user in session.", AppsmithErrorAction.LOG_EXTERNALLY, null), APPLICATION_FORKING_NOT_ALLOWED(403, 4034, "Forking this application is not permitted at this time.", AppsmithErrorAction.DEFAULT, null), GOOGLE_RECAPTCHA_TIMEOUT(504, 5042, "Google recaptcha verification timeout. Please try again.", AppsmithErrorAction.DEFAULT, null), - GOOGLE_RECAPTCHA_FAILED(401, 4034, "Google recaptcha verification failed. Please try again.", AppsmithErrorAction.DEFAULT, null), + GOOGLE_RECAPTCHA_FAILED(401, 4035, "Google recaptcha verification failed. Please try again.", AppsmithErrorAction.DEFAULT, null), UNKNOWN_ACTION_RESULT_DATA_TYPE(500, 5009, "Appsmith has encountered an unknown action result data type: {0}. " + "Please contact Appsmith customer support to resolve this.", AppsmithErrorAction.LOG_EXTERNALLY, null), + INVALID_CURL_HEADER(400, 4036, "Invalid header in cURL command: {0}.", AppsmithErrorAction.DEFAULT, null), ; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionServiceImpl.java index 8b1ce56642..d6d9aef06a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionServiceImpl.java @@ -48,7 +48,7 @@ public class ActionCollectionServiceImpl implements ActionCollectionService { .flatMap(action -> { if (action.getId() == null) { //Action doesn't exist. Create now. - return newActionService.createAction(action.getUnpublishedAction()); + return layoutActionService.createAction(action.getUnpublishedAction()); } return Mono.just(action.getUnpublishedAction()); }) @@ -77,11 +77,11 @@ public class ActionCollectionServiceImpl implements ActionCollectionService { @Override public Mono createAction(ActionDTO action) { if (action.getCollectionId() == null) { - return newActionService.createAction(action); + return layoutActionService.createAction(action); } ActionDTO finalAction = action; - return newActionService.createAction(action) + return layoutActionService.createAction(action) .flatMap(savedAction -> collectionService.addSingleActionToCollection(finalAction.getCollectionId(), savedAction)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java index 894b01ad36..11e074e079 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java @@ -385,7 +385,7 @@ public class ApplicationPageServiceImpl implements ApplicationPageService { * being set to off by default. */ AppsmithEventContext eventContext = new AppsmithEventContext(AppsmithEventContextType.CLONE_PAGE); - return newActionService.createAction( + return layoutActionService.createAction( action.getUnpublishedAction(), eventContext ); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java index 3401ecd31f..c83f61c9b1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java @@ -43,10 +43,14 @@ public class CurlImporterService extends BaseApiImporter { private final NewActionService newActionService; private final PluginService pluginService; + private final LayoutActionService layoutActionService; - public CurlImporterService(NewActionService newActionService, PluginService pluginService) { + public CurlImporterService(NewActionService newActionService, + PluginService pluginService, + LayoutActionService layoutActionService) { this.newActionService = newActionService; this.pluginService = pluginService; + this.layoutActionService = layoutActionService; } @Override @@ -79,7 +83,7 @@ public class CurlImporterService extends BaseApiImporter { datasource.setOrganizationId(orgId); return Mono.just(action1); }) - .flatMap(newActionService::createAction); + .flatMap(layoutActionService::createAction); } public ActionDTO curlToAction(String command, String pageId, String name) throws AppsmithException { @@ -290,6 +294,9 @@ public class CurlImporterService extends BaseApiImporter { } else if (ARG_HEADER.equals(state)) { // The `token` is next to `--header`. final String[] parts = token.split(":\\s*", 2); + if (parts.length != 2) { + throw new AppsmithException(AppsmithError.INVALID_CURL_HEADER, token); + } if ("content-type".equalsIgnoreCase(parts[0])) { contentType = parts[1]; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java index e79882e143..b942a8bc37 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java @@ -287,7 +287,9 @@ public class DatasourceServiceImpl extends BaseService refactorWidgetName(RefactorNameDTO refactorNameDTO); - Mono refactorActionName(RefactorNameDTO refactorNameDTO); + Mono refactorActionName(RefactorActionNameDTO refactorActionNameDTO); Mono updateAction(String id, ActionDTO action); Mono setExecuteOnLoad(String id, Boolean isExecuteOnLoad); JSONObject unescapeMongoSpecialCharacters(Layout layout); + + Mono createAction(ActionDTO action); + + Mono createAction(ActionDTO action, AppsmithEventContext eventContext); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java index ebc14157a8..cc130c0611 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java @@ -1,11 +1,15 @@ package com.appsmith.server.services; +import com.appsmith.external.helpers.AppsmithEventContext; +import com.appsmith.external.helpers.AppsmithEventContextType; import com.appsmith.external.helpers.MustacheHelper; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.server.constants.AnalyticsEvents; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.ActionDependencyEdge; +import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Layout; +import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ActionDTO; @@ -14,6 +18,7 @@ import com.appsmith.server.dtos.DslActionDTO; import com.appsmith.server.dtos.LayoutActionUpdateDTO; import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.PageDTO; +import com.appsmith.server.dtos.RefactorActionNameDTO; import com.appsmith.server.dtos.RefactorNameDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; @@ -46,6 +51,7 @@ import java.util.regex.Pattern; import static com.appsmith.external.helpers.MustacheHelper.extractWordsAndAddToSet; import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS; import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES; +import static com.appsmith.server.acl.AclPermission.READ_PAGES; import static java.util.stream.Collectors.toSet; @Service @@ -154,22 +160,23 @@ public class LayoutActionServiceImpl implements LayoutActionService { } @Override - public Mono refactorActionName(RefactorNameDTO refactorNameDTO) { - String pageId = refactorNameDTO.getPageId(); - String layoutId = refactorNameDTO.getLayoutId(); - String oldName = refactorNameDTO.getOldName(); - String newName = refactorNameDTO.getNewName(); + public Mono refactorActionName(RefactorActionNameDTO refactorActionNameDTO) { + String pageId = refactorActionNameDTO.getPageId(); + String layoutId = refactorActionNameDTO.getLayoutId(); + String oldName = refactorActionNameDTO.getOldName(); + String newName = refactorActionNameDTO.getNewName(); + String actionId = refactorActionNameDTO.getActionId(); return isNameAllowed(pageId, layoutId, newName) .flatMap(allowed -> { if (!allowed) { return Mono.error(new AppsmithException(AppsmithError.NAME_CLASH_NOT_ALLOWED_IN_REFACTOR, oldName, newName)); } return newActionService - .findByUnpublishedNameAndPageId(oldName, pageId, MANAGE_ACTIONS); + .findActionDTObyIdAndViewMode(actionId, false, MANAGE_ACTIONS); }) .flatMap(action -> { action.setName(newName); - return newActionService.updateUnpublishedAction(action.getId(), action); + return newActionService.updateUnpublishedAction(actionId, action); }) .then(refactorName(pageId, layoutId, oldName, newName)); } @@ -290,6 +297,7 @@ public class LayoutActionServiceImpl implements LayoutActionService { * in them aggregated in the field dynamicBindingsPathList. * A widget may also have other widgets as children, each of which will follow the same structure * Refer to FieldName.DEFAULT_PAGE_LAYOUT for a template + * * @param dsl * @param widgetNames * @param dynamicBindings @@ -748,4 +756,75 @@ public class LayoutActionServiceImpl implements LayoutActionService { return dsl; } + @Override + public Mono createAction(ActionDTO action) { + AppsmithEventContext eventContext = new AppsmithEventContext(AppsmithEventContextType.DEFAULT); + return createAction(action, eventContext); + } + + @Override + public Mono createAction(ActionDTO action, AppsmithEventContext eventContext) { + if (action.getId() != null) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "id")); + } + + if (action.getPageId() == null || action.getPageId().isBlank()) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.PAGE_ID)); + } + + NewAction newAction = new NewAction(); + newAction.setPublishedAction(new ActionDTO()); + newAction.getPublishedAction().setDatasource(new Datasource()); + + Mono pageMono = newPageService + .findById(action.getPageId(), READ_PAGES) + .switchIfEmpty(Mono.error( + new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.PAGE, action.getPageId()))) + .cache(); + + return pageMono + .flatMap(page -> { + Layout layout = page.getUnpublishedPage().getLayouts().get(0); + return isNameAllowed(page.getId(), layout.getId(), action.getName()); + }) + .flatMap(nameAllowed -> { + // If the name is allowed, return pageMono for further processing + if (Boolean.TRUE.equals(nameAllowed)) { + return pageMono; + } + // Throw an error since the new action's name matches an existing action or widget name. + return Mono.error(new AppsmithException(AppsmithError.DUPLICATE_KEY_USER_ERROR, action.getName(), FieldName.NAME)); + }) + .flatMap(page -> { + // Inherit the action policies from the page. + newActionService.generateAndSetActionPolicies(page, newAction); + + newActionService.setCommonFieldsFromActionDTOIntoNewAction(action, newAction); + + // Set the application id in the main domain + newAction.setApplicationId(page.getApplicationId()); + + // If the datasource is embedded, check for organizationId and set it in action + if (action.getDatasource() != null && + action.getDatasource().getId() == null) { + Datasource datasource = action.getDatasource(); + if (datasource.getOrganizationId() == null) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORGANIZATION_ID)); + } + newAction.setOrganizationId(datasource.getOrganizationId()); + } + + // New actions will never be set to auto-magical execution, unless it is triggered via a + // page or application clone event. + if (!AppsmithEventContextType.CLONE_PAGE.equals(eventContext.getAppsmithEventContextType())) { + action.setExecuteOnLoad(false); + } + + newAction.setUnpublishedAction(action); + + return Mono.just(newAction); + }) + .flatMap(newActionService::validateAndSaveActionToRepository); + } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java index 53482b9657..25bb224246 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java @@ -1,12 +1,12 @@ package com.appsmith.server.services; -import com.appsmith.external.helpers.AppsmithEventContext; +import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.domains.NewAction; +import com.appsmith.server.domains.NewPage; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionViewDTO; -import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.server.dtos.LayoutActionUpdateDTO; import org.springframework.data.domain.Sort; import org.springframework.util.MultiValueMap; @@ -19,11 +19,13 @@ import java.util.Set; public interface NewActionService extends CrudService { + void setCommonFieldsFromActionDTOIntoNewAction(ActionDTO action, NewAction newAction); + Mono generateActionByViewMode(NewAction newAction, Boolean viewMode); - Mono createAction(ActionDTO action); + void generateAndSetActionPolicies(NewPage page, NewAction action); - Mono createAction(ActionDTO action, AppsmithEventContext appsmithEventContext); + Mono validateAndSaveActionToRepository(NewAction newAction); NewAction extractAndSetJsonPathKeys(NewAction newAction); @@ -35,6 +37,8 @@ public interface NewActionService extends CrudService { Mono findByUnpublishedNameAndPageId(String name, String pageId, AclPermission permission); + Mono findActionDTObyIdAndViewMode(String id, Boolean viewMode, AclPermission permission); + Flux findUnpublishedOnLoadActionsExplicitSetByUserInPage(String pageId); Flux findUnpublishedActionsInPageByNames(Set names, String pageId); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java index 41f8ee44e0..966691338e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java @@ -1,16 +1,14 @@ package com.appsmith.server.services; +import com.appsmith.external.constants.ActionResultDataType; import com.appsmith.external.dtos.ExecuteActionDTO; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException; -import com.appsmith.external.helpers.AppsmithEventContext; -import com.appsmith.external.helpers.AppsmithEventContextType; import com.appsmith.external.helpers.MustacheHelper; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionRequest; import com.appsmith.external.models.ActionExecutionResult; -import com.appsmith.external.constants.ActionResultDataType; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Param; import com.appsmith.external.models.ParsedDataType; @@ -81,7 +79,6 @@ import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES; import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS; import static com.appsmith.server.acl.AclPermission.MANAGE_DATASOURCES; import static com.appsmith.server.acl.AclPermission.READ_ACTIONS; -import static com.appsmith.server.acl.AclPermission.READ_PAGES; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; @@ -155,7 +152,8 @@ public class NewActionServiceImpl extends BaseService documentPolicies = policyGenerator.getAllChildPolicies(page.getPolicies(), Page.class, Action.class); action.setPolicies(documentPolicies); } @Override - public Mono createAction(ActionDTO action, AppsmithEventContext eventContext) { - if (action.getId() != null) { - return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, "id")); - } - - if (action.getPageId() == null || action.getPageId().isBlank()) { - return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.PAGE_ID)); - } - - NewAction newAction = new NewAction(); - newAction.setPublishedAction(new ActionDTO()); - newAction.getPublishedAction().setDatasource(new Datasource()); - - return newPageService - .findById(action.getPageId(), READ_PAGES) - .switchIfEmpty(Mono.error( - new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.PAGE, action.getPageId()))) - .flatMap(page -> { - - // Inherit the action policies from the page. - generateAndSetActionPolicies(page, newAction); - - setCommonFieldsFromActionDTOIntoNewAction(action, newAction); - - // Set the application id in the main domain - newAction.setApplicationId(page.getApplicationId()); - - // If the datasource is embedded, check for organizationId and set it in action - if (action.getDatasource() != null && - action.getDatasource().getId() == null) { - Datasource datasource = action.getDatasource(); - if (datasource.getOrganizationId() == null) { - return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ORGANIZATION_ID)); - } - newAction.setOrganizationId(datasource.getOrganizationId()); - } - - // New actions will never be set to auto-magical execution, unless it is triggered via a - // page or application clone event. - if (!AppsmithEventContextType.CLONE_PAGE.equals(eventContext.getAppsmithEventContextType())) { - action.setExecuteOnLoad(false); - } - - newAction.setUnpublishedAction(action); - - return Mono.just(newAction); - }) - .flatMap(this::validateAndSaveActionToRepository); - } - - @Override - public Mono createAction(ActionDTO action) { - AppsmithEventContext eventContext = new AppsmithEventContext(AppsmithEventContextType.DEFAULT); - return createAction(action, eventContext); - } - - private Mono validateAndSaveActionToRepository(NewAction newAction) { + public Mono validateAndSaveActionToRepository(NewAction newAction) { ActionDTO action = newAction.getUnpublishedAction(); //Default the validity to true and invalids to be an empty set. @@ -658,17 +601,17 @@ public class NewActionServiceImpl extends BaseService { - Long timeElapsed = tuple1.getT1(); - ActionExecutionResult result = tuple1.getT2(); + Long timeElapsed = tuple1.getT1(); + ActionExecutionResult result = tuple1.getT2(); - log.debug("{}: Action {} with id {} execution time : {} ms", - Thread.currentThread().getName(), - actionName.get(), - actionId, - timeElapsed - ); + log.debug("{}: Action {} with id {} execution time : {} ms", + Thread.currentThread().getName(), + actionName.get(), + actionId, + timeElapsed + ); - return Mono.zip(actionMono, actionDTOMono, datasourceMono) + return Mono.zip(actionMono, actionDTOMono, datasourceMono) .flatMap(tuple2 -> { ActionExecutionResult actionExecutionResult = result; NewAction actionFromDb = tuple2.getT1(); @@ -706,7 +649,12 @@ public class NewActionServiceImpl extends BaseService()); return result; } @@ -736,7 +684,8 @@ public class NewActionServiceImpl extends BaseService>>() {}); + objectMapper.readValue(body, new TypeReference>>() { + }); return new ParsedDataType(ActionResultDataType.TABLE); } catch (JsonProcessingException e) { return null; @@ -760,7 +709,7 @@ public class NewActionServiceImpl extends BaseService generateActionByViewMode(action, false)); } + @Override + public Mono findActionDTObyIdAndViewMode(String id, Boolean viewMode, AclPermission permission) { + return this.findById(id, permission) + .flatMap(action -> generateActionByViewMode(action, viewMode)); + } + @Override public Flux findUnpublishedOnLoadActionsExplicitSetByUserInPage(String pageId) { return repository diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java index 8454e7a48a..fb53870782 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java @@ -320,14 +320,25 @@ public class PluginServiceImpl extends BaseService dependencyMono = loadPluginResource(pluginId, "dependency.json") + .doOnError(throwable -> + // Remove this pluginId from the cache so it is tried again next time. + formCache.remove(pluginId) + ) + .onErrorReturn(new HashMap()) + .cache(); - Mono resourceMono = Mono.zip(formMono, editorMono, settingMono) + Mono resourceMono = Mono.zip(formMono, editorMono, settingMono, dependencyMono) .map(tuple -> { Map formMap = tuple.getT1(); Map editorMap = tuple.getT2(); Map settingMap = tuple.getT3(); + Map dependencyMap = tuple.getT4(); + formMap.putAll(editorMap); formMap.putAll(settingMap); + formMap.putAll(dependencyMap); + return formMap; }); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java index 442f236461..b0728b0fc4 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserService.java @@ -1,13 +1,17 @@ package com.appsmith.server.services; import com.appsmith.server.domains.InviteUser; +import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.User; import com.appsmith.server.dtos.InviteUsersDTO; import com.appsmith.server.dtos.ResetUserPasswordDTO; +import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import java.util.HashMap; import java.util.List; +import java.util.Map; public interface UserService extends CrudService { @@ -31,4 +35,5 @@ public interface UserService extends CrudService { Mono updateCurrentUser(User updates, ServerWebExchange exchange); + Map getEmailParams(Organization organization, User inviterUser, String inviteUrl, boolean isNewUser); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index ddb10d81e5..418319ccf5 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -494,7 +494,7 @@ public class UserServiceImpl extends BaseService i public Mono sendWelcomeEmail(User user, String originHeader) { Map params = new HashMap<>(); params.put("firstName", user.getName()); - params.put("appsmithLink", originHeader); + params.put("inviteUrl", originHeader); return emailSender .sendMail(user.getEmail(), "Welcome to Appsmith", WELCOME_USER_EMAIL_TEMPLATE, params) .thenReturn(user) @@ -536,6 +536,7 @@ public class UserServiceImpl extends BaseService i * 2. User exists : * a. Add user to the organization * b. Add organization to the user + * * @return Publishes the invited users, after being saved with the new organization ID. */ @Override @@ -557,7 +558,7 @@ public class UserServiceImpl extends BaseService i List usernames = new ArrayList<>(); for (String username : originalUsernames) { - usernames.add(username.toLowerCase()); + usernames.add(username.toLowerCase()); } Mono currentUserMono = sessionUserService.getCurrentUser().cache(); @@ -588,22 +589,16 @@ public class UserServiceImpl extends BaseService i Organization organization = tuple.getT2(); User currentUser = tuple.getT3(); - // Email template parameters initialization below. - Map params = new HashMap<>(); - if (!StringUtils.isEmpty(currentUser.getName())) { - params.put("Inviter_First_Name", currentUser.getName()); - } else { - params.put("Inviter_First_Name", currentUser.getEmail()); - } - params.put("inviter_org_name", organization.getName()); - return repository.findByEmail(username) .flatMap(existingUser -> { // The user already existed, just send an email informing that the user has been added // to a new organization log.debug("Going to send email to user {} informing that the user has been added to new organization {}", existingUser.getEmail(), organization.getName()); - params.put("inviteUrl", originHeader); + + // Email template parameters initialization below. + Map params = getEmailParams(organization, currentUser, originHeader, false); + Mono emailMono = emailSender.sendMail(existingUser.getEmail(), "Appsmith: You have been added to a new organization", USER_ADDED_TO_ORGANIZATION_EMAIL_TEMPLATE, params); @@ -611,7 +606,7 @@ public class UserServiceImpl extends BaseService i return emailMono .thenReturn(existingUser); }) - .switchIfEmpty(createNewUserAndSendInviteEmail(username, originHeader, params)); + .switchIfEmpty(createNewUserAndSendInviteEmail(username, originHeader, organization, currentUser)); }) .cache(); @@ -657,7 +652,7 @@ public class UserServiceImpl extends BaseService i ); } - private Mono createNewUserAndSendInviteEmail(String email, String originHeader, Map params) { + private Mono createNewUserAndSendInviteEmail(String email, String originHeader, Organization organization, User inviter) { User newUser = new User(); newUser.setEmail(email.toLowerCase()); @@ -678,7 +673,9 @@ public class UserServiceImpl extends BaseService i URLEncoder.encode(createdUser.getEmail(), StandardCharsets.UTF_8) ); - params.put("inviteUrl", inviteUrl); + // Email template parameters initialization below. + Map params = getEmailParams(organization, inviter, inviteUrl, true); + Mono emailMono = emailSender.sendMail(createdUser.getEmail(), "Invite for Appsmith", INVITE_USER_EMAIL_TEMPLATE, params); // We have sent out the emails. Just send back the saved user. @@ -742,4 +739,25 @@ public class UserServiceImpl extends BaseService i ); } + public Map getEmailParams(Organization organization, User inviter, String inviteUrl, boolean isNewUser) { + Map params = new HashMap<>(); + + if (inviter != null) { + if (!StringUtils.isEmpty(inviter.getName())) { + params.put("Inviter_First_Name", inviter.getName()); + } else { + params.put("Inviter_First_Name", inviter.getEmail()); + } + } + if (organization != null) { + params.put("inviter_org_name", organization.getName()); + } + if (isNewUser) { + params.put("inviteUrl", inviteUrl); + } else { + params.put("inviteUrl", inviteUrl + "/applications#" + organization.getSlug()); + } + return params; + } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java index f7ee8a397c..abd99f4e91 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java @@ -23,6 +23,7 @@ import com.appsmith.server.services.ApplicationService; import com.appsmith.server.services.ConfigService; import com.appsmith.server.services.DatasourceContextService; import com.appsmith.server.services.DatasourceService; +import com.appsmith.server.services.LayoutActionService; import com.appsmith.server.services.NewActionService; import com.appsmith.server.services.OrganizationService; import com.appsmith.server.services.SessionUserService; @@ -62,6 +63,7 @@ public class ExamplesOrganizationCloner { private final DatasourceContextService datasourceContextService; private final NewPageRepository newPageRepository; private final NewActionService newActionService; + private final LayoutActionService layoutActionService; public Mono cloneExamplesOrganization() { return sessionUserService @@ -258,7 +260,7 @@ public class ExamplesOrganizationCloner { } } return actionMono - .flatMap(newActionService::createAction) + .flatMap(layoutActionService::createAction) .map(ActionDTO::getId) .zipWith(Mono.justOrEmpty(originalActionId)); }) diff --git a/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html b/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html index 4a3be65b33..4ae1d91eaa 100644 --- a/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html +++ b/app/server/appsmith-server/src/main/resources/email/welcomeUserTemplate.html @@ -374,7 +374,7 @@ - Go to Appsmith + Go to Appsmith diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java index d78db8e1e5..72c8755de6 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java @@ -213,7 +213,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - Mono actionMono = newActionService.createAction(action) + Mono actionMono = layoutActionService.createAction(action) .flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS)) .flatMap(newAction -> newActionService.generateActionByViewMode(newAction, false)); @@ -246,7 +246,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - Mono createActionMono = newActionService.createAction(action).cache(); + Mono createActionMono = layoutActionService.createAction(action).cache(); Mono movedActionMono = createActionMono .flatMap(savedAction -> { @@ -285,7 +285,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); Mono actionMono = Mono.just(action) - .flatMap(newActionService::createAction); + .flatMap(layoutActionService::createAction); StepVerifier .create(actionMono) .assertNext(createdAction -> { @@ -306,7 +306,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setDatasource(datasource); Mono actionMono = Mono.just(action) - .flatMap(newActionService::createAction); + .flatMap(layoutActionService::createAction); StepVerifier .create(actionMono) .assertNext(createdAction -> { @@ -328,7 +328,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); Mono actionMono = Mono.just(action) - .flatMap(newActionService::createAction); + .flatMap(layoutActionService::createAction); StepVerifier .create(actionMono) .expectErrorMatches(throwable -> throwable instanceof AppsmithException && @@ -345,7 +345,7 @@ public class ActionServiceTest { actionConfiguration.setHttpMethod(HttpMethod.GET); action.setActionConfiguration(actionConfiguration); Mono actionMono = Mono.just(action) - .flatMap(newActionService::createAction); + .flatMap(layoutActionService::createAction); StepVerifier .create(actionMono) .expectErrorMatches(throwable -> throwable instanceof AppsmithException && @@ -363,7 +363,7 @@ public class ActionServiceTest { actionConfiguration.setHttpMethod(HttpMethod.GET); action.setActionConfiguration(actionConfiguration); Mono actionMono = Mono.just(action) - .flatMap(newActionService::createAction); + .flatMap(layoutActionService::createAction); StepVerifier .create(actionMono) .expectErrorMatches(throwable -> throwable instanceof AppsmithException && @@ -448,7 +448,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("testActionExecute"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -476,7 +476,7 @@ public class ActionServiceTest { action.setName("testActionExecuteNullRequestBody"); action.setPageId(testPage.getId()); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -501,7 +501,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("testActionExecuteDbQuery"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -530,7 +530,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("testActionExecuteErrorResponse"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -575,7 +575,7 @@ public class ActionServiceTest { DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); datasource.setDatasourceConfiguration(datasourceConfiguration); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -618,7 +618,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("testActionExecuteSecondaryStaleConnection"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -660,7 +660,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("testActionExecuteTimeout"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -711,9 +711,9 @@ public class ActionServiceTest { action2.setPageId(testPage.getId()); action2.setDatasource(datasource); - Mono> actionsListMono = newActionService.createAction(action) - .then(newActionService.createAction(action1)) - .then(newActionService.createAction(action2)) + Mono> actionsListMono = layoutActionService.createAction(action) + .then(layoutActionService.createAction(action1)) + .then(layoutActionService.createAction(action2)) .then(applicationPageService.publish(testPage.getApplicationId())) .then(newActionService.getActionsForViewMode(testApp.getId()).collectList()); @@ -765,7 +765,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("checkRecoveryFromStaleConnections"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -818,7 +818,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - Mono createActionMono = newActionService.createAction(action); + Mono createActionMono = layoutActionService.createAction(action); Mono> actionViewModeListMono = createActionMono // Publish the application before fetching the action in view mode .then(applicationPageService.publish(testApp.getId())) @@ -861,7 +861,7 @@ public class ActionServiceTest { action.setDatasource(savedDs); - Mono resultMono = newActionService.createAction(action) + Mono resultMono = layoutActionService.createAction(action) .flatMap(savedAction -> { ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(savedAction.getId()); @@ -902,7 +902,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(savedDs); - Mono newActionMono = newActionService + Mono newActionMono = layoutActionService .createAction(action) .cache(); @@ -985,7 +985,7 @@ public class ActionServiceTest { actionConfiguration1.setHttpMethod(HttpMethod.GET); action.setActionConfiguration(actionConfiguration1); - ActionDTO savedAction = newActionService.createAction(action).block(); + ActionDTO savedAction = layoutActionService.createAction(action).block(); Mono datasourceMono = publicAppMono .then(datasourceService.findById(savedDatasource.getId())); @@ -1023,7 +1023,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - Mono actionMono = newActionService.createAction(action); + Mono actionMono = layoutActionService.createAction(action); StepVerifier .create(actionMono) .assertNext(createdAction -> { @@ -1048,7 +1048,7 @@ public class ActionServiceTest { action.setDatasource(datasource); AppsmithEventContext eventContext = new AppsmithEventContext(AppsmithEventContextType.CLONE_PAGE); - Mono actionMono = newActionService.createAction(action, eventContext); + Mono actionMono = layoutActionService.createAction(action, eventContext); StepVerifier .create(actionMono) .assertNext(createdAction -> { @@ -1071,7 +1071,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - Mono newActionMono = newActionService + Mono newActionMono = layoutActionService .createAction(action); Mono updateActionMono = newActionMono @@ -1108,7 +1108,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - Mono newActionMono = newActionService + Mono newActionMono = layoutActionService .createAction(action); Mono updateActionMono = newActionMono @@ -1146,7 +1146,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - Mono actionMono = newActionService.createAction(action) + Mono actionMono = layoutActionService.createAction(action) .flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS)) .flatMap(newAction -> newActionService.generateActionByViewMode(newAction, false)); @@ -1179,7 +1179,7 @@ public class ActionServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - Mono actionMono = newActionService.createAction(action) + Mono actionMono = layoutActionService.createAction(action) .flatMap(createdAction -> newActionService.findById(createdAction.getId(), READ_ACTIONS)) .flatMap(newAction -> newActionService.generateActionByViewMode(newAction, false)); @@ -1220,7 +1220,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("testActionExecute"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -1259,7 +1259,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("testActionExecute"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -1298,7 +1298,7 @@ public class ActionServiceTest { action.setPageId(testPage.getId()); action.setName("testActionExecute"); action.setDatasource(datasource); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); executeActionDTO.setActionId(createdAction.getId()); @@ -1307,4 +1307,33 @@ public class ActionServiceTest { executeAndAssertAction(executeActionDTO, actionConfiguration, mockResult, List.of(new ParsedDataType(ActionResultDataType.RAW))); } + + @Test + @WithUserDetails(value = "api_user") + public void testActionExecuteReturnTypeWithNullResultBody() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(pluginExecutor)); + + ActionExecutionResult mockResult = new ActionExecutionResult(); + mockResult.setIsExecutionSuccess(true); + mockResult.setBody(null); + mockResult.setStatusCode("200"); + mockResult.setHeaders(objectMapper.valueToTree(Map.of("response-header-key", "response-header-value"))); + + ActionDTO action = new ActionDTO(); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.POST); + actionConfiguration.setBody("random-request-body"); + actionConfiguration.setHeaders(List.of(new Property("random-header-key", "random-header-value"))); + action.setActionConfiguration(actionConfiguration); + action.setPageId(testPage.getId()); + action.setName("testActionExecute"); + action.setDatasource(datasource); + ActionDTO createdAction = layoutActionService.createAction(action).block(); + + ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); + executeActionDTO.setActionId(createdAction.getId()); + executeActionDTO.setViewMode(false); + + executeAndAssertAction(executeActionDTO, actionConfiguration, mockResult, new ArrayList<>()); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java index dbff513123..4bcf8ebe5c 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java @@ -105,6 +105,9 @@ public class ApplicationServiceTest { @Autowired ApplicationRepository applicationRepository; + @Autowired + LayoutActionService layoutActionService; + @MockBean ReleaseNotesService releaseNotesService; @@ -553,7 +556,7 @@ public class ApplicationServiceTest { actionConfiguration.setHttpMethod(HttpMethod.GET); action.setActionConfiguration(actionConfiguration); - ActionDTO savedAction = newActionService.createAction(action).block(); + ActionDTO savedAction = layoutActionService.createAction(action).block(); ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO(); applicationAccessDTO.setPublicAccess(true); @@ -908,7 +911,7 @@ public class ApplicationServiceTest { actionConfiguration.setHttpMethod(HttpMethod.GET); action1.setActionConfiguration(actionConfiguration); - ActionDTO savedAction1 = newActionService.createAction(action1).block(); + ActionDTO savedAction1 = layoutActionService.createAction(action1).block(); ActionDTO action2 = new ActionDTO(); action2.setName("Clone App Test action2"); @@ -916,7 +919,7 @@ public class ApplicationServiceTest { action2.setDatasource(savedDatasource); action2.setActionConfiguration(actionConfiguration); - ActionDTO savedAction2 = newActionService.createAction(action2).block(); + ActionDTO savedAction2 = layoutActionService.createAction(action2).block(); ActionDTO action3 = new ActionDTO(); action3.setName("Clone App Test action3"); @@ -924,8 +927,7 @@ public class ApplicationServiceTest { action3.setDatasource(savedDatasource); action3.setActionConfiguration(actionConfiguration); - ActionDTO savedAction3 = newActionService.createAction(action3).block(); - + ActionDTO savedAction3 = layoutActionService.createAction(action3).block(); // Trigger the clone of application now. applicationPageService.cloneApplication(originalApplication.getId()) diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java index a072003c32..89d59d5511 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java @@ -30,6 +30,7 @@ import reactor.test.StepVerifier; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; @RunWith(SpringRunner.class) @SpringBootTest @@ -677,6 +678,24 @@ public class CurlImporterServiceTest { ); } + @Test + public void importInvalidMethod() { + assertThatThrownBy(() -> { + curlImporterService.curlToAction("curl -X invalid-method http://httpbin.org/get"); + }) + .isInstanceOf(AppsmithException.class) + .matches(err -> ((AppsmithException) err).getError() == AppsmithError.INVALID_CURL_METHOD); + } + + @Test + public void importInvalidHeader() { + assertThatThrownBy(() -> { + curlImporterService.curlToAction("curl -H x-custom http://httpbin.org/headers"); + }) + .isInstanceOf(AppsmithException.class) + .matches(err -> ((AppsmithException) err).getError() == AppsmithError.INVALID_CURL_HEADER); + } + @Test @WithUserDetails(value = "api_user") public void importInvalidCurlCommand() { diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java index fc9c70ac3d..44f85358a0 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java @@ -74,6 +74,9 @@ public class DatasourceServiceTest { @Autowired EncryptionService encryptionService; + @Autowired + LayoutActionService layoutActionService; + @MockBean PluginExecutorHelper pluginExecutorHelper; @@ -529,7 +532,7 @@ public class DatasourceServiceTest { action.setActionConfiguration(actionConfiguration); action.setDatasource(datasource); - return newActionService.createAction(action).thenReturn(datasource); + return layoutActionService.createAction(action).thenReturn(datasource); }) .flatMap(datasource -> datasourceService.delete(datasource.getId())); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java index 8ea5cf0c50..805d733a1f 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java @@ -16,9 +16,12 @@ import com.appsmith.server.dtos.DslActionDTO; import com.appsmith.server.dtos.LayoutActionUpdateDTO; import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.PageDTO; -import com.appsmith.server.dtos.RefactorNameDTO; +import com.appsmith.server.dtos.RefactorActionNameDTO; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.MockPluginExecutor; import com.appsmith.server.helpers.PluginExecutorHelper; +import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.repositories.OrganizationRepository; import com.appsmith.server.repositories.PluginRepository; import lombok.extern.slf4j.Slf4j; @@ -87,6 +90,9 @@ public class LayoutActionServiceTest { @Autowired NewPageService newPageService; + @Autowired + NewActionRepository actionRepository; + Application testApp = null; PageDTO testPage = null; @@ -180,7 +186,7 @@ public class LayoutActionServiceTest { unreferencedAction.setActionConfiguration(actionConfiguration2); unreferencedAction.setDatasource(datasource); - Mono resultMono = newActionService + Mono resultMono = layoutActionService .createAction(action) .flatMap(savedAction -> { ActionDTO updates = new ActionDTO(); @@ -190,7 +196,7 @@ public class LayoutActionServiceTest { updates.setDatasource(datasource); return layoutActionService.updateAction(savedAction.getId(), updates); }) - .flatMap(savedAction -> newActionService.createAction(unreferencedAction)) + .flatMap(savedAction -> layoutActionService.createAction(unreferencedAction)) .flatMap(savedAction -> { ActionDTO updates = new ActionDTO(); updates.setExecuteOnLoad(true); @@ -237,18 +243,19 @@ public class LayoutActionServiceTest { layout.setDsl(dsl); layout.setPublishedDsl(dsl); - ActionDTO createdAction = newActionService.createAction(action).block(); + ActionDTO createdAction = layoutActionService.createAction(action).block(); LayoutDTO firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); - RefactorNameDTO refactorNameDTO = new RefactorNameDTO(); - refactorNameDTO.setPageId(testPage.getId()); - refactorNameDTO.setLayoutId(firstLayout.getId()); - refactorNameDTO.setOldName("beforeNameChange"); - refactorNameDTO.setNewName("PostNameChange"); + RefactorActionNameDTO refactorActionNameDTO = new RefactorActionNameDTO(); + refactorActionNameDTO.setPageId(testPage.getId()); + refactorActionNameDTO.setLayoutId(firstLayout.getId()); + refactorActionNameDTO.setOldName("beforeNameChange"); + refactorActionNameDTO.setNewName("PostNameChange"); + refactorActionNameDTO.setActionId(createdAction.getId()); - LayoutDTO postNameChangeLayout = layoutActionService.refactorActionName(refactorNameDTO).block(); + LayoutDTO postNameChangeLayout = layoutActionService.refactorActionName(refactorActionNameDTO).block(); Mono postNameChangeActionMono = newActionService.findById(createdAction.getId(), READ_ACTIONS); @@ -282,7 +289,7 @@ public class LayoutActionServiceTest { Layout layout = testPage.getLayouts().get(0); - ActionDTO firstAction = newActionService.createAction(action).block(); + ActionDTO firstAction = layoutActionService.createAction(action).block(); layout.setDsl(layoutActionService.unescapeMongoSpecialCharacters(layout)); LayoutDTO firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); @@ -293,15 +300,16 @@ public class LayoutActionServiceTest { // Create another action with the same name as the erstwhile deleted action action.setId(null); - ActionDTO secondAction = newActionService.createAction(action).block(); + ActionDTO secondAction = layoutActionService.createAction(action).block(); - RefactorNameDTO refactorNameDTO = new RefactorNameDTO(); - refactorNameDTO.setPageId(testPage.getId()); - refactorNameDTO.setLayoutId(firstLayout.getId()); - refactorNameDTO.setOldName("Query1"); - refactorNameDTO.setNewName("NewActionName"); + RefactorActionNameDTO refactorActionNameDTO = new RefactorActionNameDTO(); + refactorActionNameDTO.setPageId(testPage.getId()); + refactorActionNameDTO.setLayoutId(firstLayout.getId()); + refactorActionNameDTO.setOldName("Query1"); + refactorActionNameDTO.setNewName("NewActionName"); + refactorActionNameDTO.setActionId(firstAction.getId()); - layoutActionService.refactorActionName(refactorNameDTO).block(); + layoutActionService.refactorActionName(refactorActionNameDTO).block(); Mono postNameChangeActionMono = newActionService.findById(secondAction.getId(), READ_ACTIONS); @@ -346,8 +354,8 @@ public class LayoutActionServiceTest { Layout layout = testPage.getLayouts().get(0); layout.setDsl(dsl); - ActionDTO createdAction1 = newActionService.createAction(action1).block(); - ActionDTO createdAction2 = newActionService.createAction(action2).block(); + ActionDTO createdAction1 = layoutActionService.createAction(action1).block(); + ActionDTO createdAction2 = layoutActionService.createAction(action2).block(); Mono updateLayoutMono = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout); @@ -432,7 +440,7 @@ public class LayoutActionServiceTest { unreferencedAction.setActionConfiguration(actionConfiguration2); unreferencedAction.setDatasource(datasource); - Mono resultMono = newActionService + Mono resultMono = layoutActionService .createAction(action) .flatMap(savedAction -> { ActionDTO updates = new ActionDTO(); @@ -498,8 +506,114 @@ public class LayoutActionServiceTest { assertThat(primaryColumns2.keySet()).containsAll(Set.of(FieldName.MONGO_ESCAPE_ID, FieldName.MONGO_ESCAPE_CLASS)); }) .verifyComplete(); - - } + @Test + @WithUserDetails(value = "api_user") + public void refactorDuplicateActionName() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor())); + + String name = "duplicateName"; + + ActionDTO action = new ActionDTO(); + action.setName(name); + action.setPageId(testPage.getId()); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); + + JSONObject dsl = new JSONObject(); + dsl.put("widgetName", "firstWidget"); + JSONArray temp = new JSONArray(); + temp.addAll(List.of(new JSONObject(Map.of("key", "testField")))); + dsl.put("dynamicBindingPathList", temp); + dsl.put("testField", "{{ duplicateName.data }}"); + + Layout layout = testPage.getLayouts().get(0); + layout.setDsl(dsl); + layout.setPublishedDsl(dsl); + + ActionDTO firstAction = layoutActionService.createAction(action).block(); + + ActionDTO duplicateName = new ActionDTO(); + duplicateName.setName(name); + duplicateName.setPageId(testPage.getId()); + duplicateName.setActionConfiguration(actionConfiguration); + duplicateName.setDatasource(datasource); + + NewAction duplicateNameCompleteAction = new NewAction(); + duplicateNameCompleteAction.setUnpublishedAction(duplicateName); + duplicateNameCompleteAction.setPublishedAction(new ActionDTO()); + duplicateNameCompleteAction.getPublishedAction().setDatasource(new Datasource()); + duplicateNameCompleteAction.setOrganizationId(duplicateName.getOrganizationId()); + duplicateNameCompleteAction.setPluginType(duplicateName.getPluginType()); + duplicateNameCompleteAction.setPluginId(duplicateName.getPluginId()); + duplicateNameCompleteAction.setTemplateId(duplicateName.getTemplateId()); + duplicateNameCompleteAction.setProviderId(duplicateName.getProviderId()); + duplicateNameCompleteAction.setDocumentation(duplicateName.getDocumentation()); + duplicateNameCompleteAction.setApplicationId(duplicateName.getApplicationId()); + + // Now save this action directly in the repo to create a duplicate action name scenario + actionRepository.save(duplicateNameCompleteAction).block(); + + LayoutDTO firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); + + RefactorActionNameDTO refactorActionNameDTO = new RefactorActionNameDTO(); + refactorActionNameDTO.setPageId(testPage.getId()); + refactorActionNameDTO.setLayoutId(firstLayout.getId()); + refactorActionNameDTO.setOldName("duplicateName"); + refactorActionNameDTO.setNewName("newName"); + refactorActionNameDTO.setActionId(firstAction.getId()); + + LayoutDTO postNameChangeLayout = layoutActionService.refactorActionName(refactorActionNameDTO).block(); + + Mono postNameChangeActionMono = newActionService.findById(firstAction.getId(), READ_ACTIONS); + + StepVerifier + .create(postNameChangeActionMono) + .assertNext(updatedAction -> { + + assertThat(updatedAction.getUnpublishedAction().getName()).isEqualTo("newName"); + + DslActionDTO actionDTO = postNameChangeLayout.getLayoutOnLoadActions().get(0).iterator().next(); + assertThat(actionDTO.getName()).isEqualTo("newName"); + + dsl.put("testField", "{{ newName.data }}"); + assertThat(postNameChangeLayout.getDsl()).isEqualTo(dsl); + }) + .verifyComplete(); + } + + @Test + @WithUserDetails(value = "api_user") + public void duplicateActionNameCreation() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor())); + + String name = "query1"; + + ActionDTO action = new ActionDTO(); + action.setName(name); + action.setPageId(testPage.getId()); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); + + layoutActionService.createAction(action).block(); + + ActionDTO duplicateAction = new ActionDTO(); + duplicateAction.setName(name); + duplicateAction.setPageId(testPage.getId()); + duplicateAction.setActionConfiguration(actionConfiguration); + duplicateAction.setDatasource(datasource); + + Mono duplicateActionMono = layoutActionService.createAction(duplicateAction); + + StepVerifier + .create(duplicateActionMono) + .expectErrorMatches(throwable -> throwable instanceof AppsmithException && + throwable.getMessage().equals(AppsmithError.DUPLICATE_KEY_USER_ERROR.getMessage(name, FieldName.NAME))) + .verify(); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java index 64ac1e9e77..c6c802121c 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java @@ -285,7 +285,7 @@ public class LayoutServiceTest { action.getActionConfiguration().setHttpMethod(HttpMethod.GET); action.setPageId(page1.getId()); action.setDatasource(datasource); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); action = new ActionDTO(); action.setName("aPostAction"); @@ -293,7 +293,7 @@ public class LayoutServiceTest { action.getActionConfiguration().setHttpMethod(HttpMethod.POST); action.setPageId(page1.getId()); action.setDatasource(datasource); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); action = new ActionDTO(); action.setName("aPostActionWithAutoExec"); @@ -305,7 +305,7 @@ public class LayoutServiceTest { action.setPageId(page1.getId()); action.setExecuteOnLoad(true); action.setDatasource(datasource); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); action = new ActionDTO(); action.setName("aPostSecondaryAction"); @@ -314,7 +314,7 @@ public class LayoutServiceTest { action.setPageId(page1.getId()); action.setDatasource(datasource); action.setUserSetOnLoad(true); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); action = new ActionDTO(); action.setName("aPostTertiaryAction"); @@ -323,7 +323,7 @@ public class LayoutServiceTest { action.setPageId(page1.getId()); action.setExecuteOnLoad(true); action.setDatasource(datasource); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); action = new ActionDTO(); action.setName("aDeleteAction"); @@ -331,7 +331,7 @@ public class LayoutServiceTest { action.getActionConfiguration().setHttpMethod(HttpMethod.DELETE); action.setPageId(page1.getId()); action.setDatasource(datasource); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); action = new ActionDTO(); action.setName("aDBAction"); @@ -340,7 +340,7 @@ public class LayoutServiceTest { action.setExecuteOnLoad(true); action.setDatasource(datasource); action.setPluginType(PluginType.DB); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); action = new ActionDTO(); action.setName("anotherDBAction"); @@ -349,7 +349,7 @@ public class LayoutServiceTest { action.setExecuteOnLoad(true); action.setDatasource(datasource); action.setPluginType(PluginType.DB); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); action = new ActionDTO(); action.setName("aTableAction"); @@ -358,7 +358,7 @@ public class LayoutServiceTest { action.setExecuteOnLoad(true); action.setDatasource(datasource); action.setPluginType(PluginType.DB); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); return Mono.zip(monos, objects -> page1); }) @@ -475,7 +475,7 @@ public class LayoutServiceTest { action.getActionConfiguration().setHttpMethod(HttpMethod.GET); action.setPageId(page1.getId()); action.setDatasource(datasource); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); return Mono.zip(monos, objects -> page1); }) @@ -550,7 +550,7 @@ public class LayoutServiceTest { action.getActionConfiguration().setHttpMethod(HttpMethod.GET); action.setPageId(page1.getId()); action.setDatasource(datasource); - monos.add(newActionService.createAction(action)); + monos.add(layoutActionService.createAction(action)); return Mono.zip(monos, objects -> page1); }) diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java index de69dbbb48..da49dcf673 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java @@ -306,7 +306,7 @@ public class PageServiceTest { final LayoutDTO layoutDTO = layoutActionService.updateLayout(page.getId(), layout.getId(), layout).block(); - newActionService.createAction(action).block(); + layoutActionService.createAction(action).block(); final Mono pageMono = applicationPageService.clonePage(page.getId()).cache(); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PluginServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PluginServiceTest.java index ff5f835562..b34ffb9ef7 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PluginServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PluginServiceTest.java @@ -70,6 +70,8 @@ public class PluginServiceTest { .thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL))); Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("setting.json"))) .thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL))); + Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("dependency.json"))) + .thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL))); Mono formConfig = pluginService.getFormConfig("random-plugin-id"); @@ -90,6 +92,8 @@ public class PluginServiceTest { .thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL))); Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("setting.json"))) .thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL))); + Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("dependency.json"))) + .thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL))); Mono formConfig = pluginService.getFormConfig("random-plugin-id"); StepVerifier.create(formConfig) @@ -98,10 +102,12 @@ public class PluginServiceTest { assertThat(form.get("form")).isNotNull(); assertThat(form.get("editor")).isNull(); assertThat(form.get("setting")).isNull(); + assertThat(form.get("dependencies")).isNull(); }) .verifyComplete(); } + @Test public void getPluginFormValid() { Map formMap = new HashMap(); @@ -113,12 +119,17 @@ public class PluginServiceTest { Map settingMap = new HashMap(); settingMap.put("setting", new Object()); + Map dependencyMap = new HashMap(); + dependencyMap.put("dependencies", new Object()); + Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("form.json"))) .thenReturn(Mono.just(formMap)); Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("editor.json"))) .thenReturn(Mono.just(editorMap)); Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("setting.json"))) .thenReturn(Mono.just(settingMap)); + Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("dependency.json"))) + .thenReturn(Mono.just(dependencyMap)); Mono formConfig = pluginService.getFormConfig("random-plugin-id"); StepVerifier.create(formConfig) diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java index 34d303cbd5..642ed5cc70 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/UserServiceTest.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import static com.appsmith.server.acl.AclPermission.MANAGE_APPLICATIONS; @@ -43,6 +44,7 @@ import static com.appsmith.server.acl.AclPermission.READ_USERS; import static com.appsmith.server.acl.AclPermission.USER_MANAGE_ORGANIZATIONS; import static com.appsmith.server.acl.AclPermission.USER_READ_ORGANIZATIONS; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @Slf4j @@ -79,6 +81,42 @@ public class UserServiceTest { organizationMono = organizationService.getBySlug("spring-test-organization"); } + //Test if email params are updating correctly + @Test + public void checkEmailParamsForExistingUser() { + Organization organization = new Organization(); + organization.setName("UserServiceTest Update Org"); + organization.setSlug("userservicetest-update-org"); + + User inviter = new User(); + inviter.setName("inviterUserToApplication"); + + String inviteUrl = "http://localhost:8080"; + String expectedUrl = inviteUrl + "/applications#userservicetest-update-org"; + + Map params = userService.getEmailParams(organization, inviter, inviteUrl, false); + assertEquals(expectedUrl, params.get("inviteUrl")); + assertEquals("inviterUserToApplication", params.get("Inviter_First_Name")); + assertEquals("UserServiceTest Update Org", params.get("inviter_org_name")); + } + + @Test + public void checkEmailParamsForNewUser() { + Organization organization = new Organization(); + organization.setName("UserServiceTest Update Org"); + organization.setSlug("userservicetest-update-org"); + + User inviter = new User(); + inviter.setName("inviterUserToApplication"); + + String inviteUrl = "http://localhost:8080"; + + Map params = userService.getEmailParams(organization, inviter, inviteUrl, true); + assertEquals(inviteUrl, params.get("inviteUrl")); + assertEquals("inviterUserToApplication", params.get("Inviter_First_Name")); + assertEquals("UserServiceTest Update Org", params.get("inviter_org_name")); + } + //Test the update organization flow. @Test public void updateInvalidUserWithAnything() { diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ShareOrganizationPermissionTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ShareOrganizationPermissionTests.java index b5a5ec47aa..2817ffc490 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ShareOrganizationPermissionTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ShareOrganizationPermissionTests.java @@ -19,6 +19,7 @@ import com.appsmith.server.repositories.ApplicationRepository; import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.ApplicationService; import com.appsmith.server.services.DatasourceService; +import com.appsmith.server.services.LayoutActionService; import com.appsmith.server.services.NewActionService; import com.appsmith.server.services.NewPageService; import com.appsmith.server.services.OrganizationService; @@ -95,6 +96,9 @@ public class ShareOrganizationPermissionTests { @Autowired NewPageService newPageService; + @Autowired + LayoutActionService layoutActionService; + @MockBean PluginExecutorHelper pluginExecutorHelper; @@ -243,7 +247,7 @@ public class ShareOrganizationPermissionTests { actionConfiguration.setHttpMethod(HttpMethod.GET); action1.setActionConfiguration(actionConfiguration); - ActionDTO savedAction1 = newActionService.createAction(action1).block(); + ActionDTO savedAction1 = layoutActionService.createAction(action1).block(); ActionDTO action2 = new ActionDTO(); action2.setName("Invite Cancellation Test action2"); @@ -251,7 +255,7 @@ public class ShareOrganizationPermissionTests { action2.setDatasource(savedDatasource); action2.setActionConfiguration(actionConfiguration); - ActionDTO savedAction2 = newActionService.createAction(action2).block(); + ActionDTO savedAction2 = layoutActionService.createAction(action2).block(); ActionDTO action3 = new ActionDTO(); action3.setName("Invite Cancellation Test action3"); @@ -259,7 +263,7 @@ public class ShareOrganizationPermissionTests { action3.setDatasource(savedDatasource); action3.setActionConfiguration(actionConfiguration); - ActionDTO savedAction3 = newActionService.createAction(action3).block(); + ActionDTO savedAction3 = layoutActionService.createAction(action3).block(); InviteUsersDTO inviteUsersDTO = new InviteUsersDTO(); inviteUsersDTO.setOrgId(savedOrganization.getId()); diff --git a/deploy/install.sh b/deploy/install.sh index 6de6bbac89..a326e38cce 100755 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -29,7 +29,7 @@ check_ports_occupied() { if [[ -n $port_check_output ]]; then curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Error", "data": { @@ -95,7 +95,7 @@ install_docker_compose() { else curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Error", "data": { @@ -370,7 +370,7 @@ bye() { # Prints a friendly good bye message and exits the script. curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Support", "data": { @@ -401,7 +401,7 @@ ask_telemetry() { curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Telemetry", "data": { @@ -427,7 +427,7 @@ trap bye EXIT curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ ---data-raw '{ +--data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Started", "data": { @@ -442,7 +442,7 @@ if [[ $desired_os -eq 0 ]];then echo_contact_support " if you wish to extend this support." curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Error", "data": { @@ -454,7 +454,7 @@ if [[ $desired_os -eq 0 ]];then else curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "OS Check Passed", "data": { @@ -472,7 +472,7 @@ if [[ $EUID -eq 0 ]]; then echo "++++++++++++++++++++++++++++++++++++++++" curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Error", "data": { @@ -484,7 +484,7 @@ if [[ $EUID -eq 0 ]]; then fi curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ ---data-raw '{ +--data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Root Check Passed", "data": { @@ -497,7 +497,7 @@ check_ports_occupied curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ ---data-raw '{ +--data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Port Check Passed", "data": { @@ -519,7 +519,7 @@ if [[ -e "$install_dir" ]]; then echo_contact_support " if you're facing problems with the auto-updates." curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Error", "data": { @@ -533,7 +533,7 @@ fi curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ ---data-raw '{ +--data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Directory Check Passed", "data": { @@ -554,7 +554,7 @@ if ! is_command_present docker; then echo "++++++++++++++++++++++++++++++++++++++++++++++++" curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Error", "data": { @@ -568,7 +568,7 @@ fi curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ ---data-raw '{ +--data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Docker Check Passed", "data": { @@ -594,7 +594,7 @@ echo "" if confirm y "Is this a fresh installation?"; then curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Fresh Install", "data": { @@ -614,7 +614,7 @@ if confirm y "Is this a fresh installation?"; then else curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Existing Installation" }' > /dev/null @@ -676,7 +676,7 @@ echo "" curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ ---data-raw '{ +--data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Salt Generation Done", "data": { @@ -688,7 +688,7 @@ curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30o if confirm n "Do you have a custom domain that you would like to link? (Only for cloud installations)"; then curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Custom Domain", "data": { @@ -706,7 +706,7 @@ if confirm n "Do you have a custom domain that you would like to link? (Only for if confirm y '(Your DNS records must be updated for us to proceed)'; then curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "SSL Provisioning Start", "data": { @@ -762,7 +762,7 @@ echo "" curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Config Files Generated", "data": { @@ -802,7 +802,7 @@ if [[ $status_code -ne 401 ]]; then echo "++++++++++++++++++++++++++++++++++++++++" curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Error", "data": { @@ -814,7 +814,7 @@ if [[ $status_code -ne 401 ]]; then else curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Installation Success", "data": { @@ -840,7 +840,7 @@ else read -rp 'Email: ' email curl -s --location --request POST 'https://hook.integromat.com/dkwb6i52am93pi30ojeboktvj32iw0fa' \ --header 'Content-Type: text/plain' \ - --data-raw '{ + --data '{ "userId": "'"$APPSMITH_INSTALLATION_ID"'", "event": "Identify Successful Installation", "data": {