From 4778b0a45057668e2c6dac0fa773c343f9964333 Mon Sep 17 00:00:00 2001 From: Vicky Bansal <67091118+vicky-primathon@users.noreply.github.com> Date: Tue, 13 Apr 2021 16:33:13 +0530 Subject: [PATCH 01/32] Fix Table column names allowed to have spaces when editing in property pane panel titles (#3869) Added cypress test to validate name formatting --- .../DisplayWidgets/Table_GeneralProperty_spec.js | 8 ++++++++ app/client/src/pages/Editor/PropertyPaneTitle.tsx | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) 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/src/pages/Editor/PropertyPaneTitle.tsx b/app/client/src/pages/Editor/PropertyPaneTitle.tsx index 800ffd261b..5543c7c982 100644 --- a/app/client/src/pages/Editor/PropertyPaneTitle.tsx +++ b/app/client/src/pages/Editor/PropertyPaneTitle.tsx @@ -166,7 +166,9 @@ const PropertyPaneTitle = memo((props: PropertyPaneTitleProps) => { )} Date: Tue, 13 Apr 2021 19:58:40 +0530 Subject: [PATCH 02/32] fix NPE by adding null check conditional (#3989) Fix NPE seen on Sentry by adding null check for result body --- .../server/services/NewActionServiceImpl.java | 5 ++++ .../server/services/ActionServiceTest.java | 29 +++++++++++++++++++ 2 files changed, 34 insertions(+) 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..bac9d46174 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 @@ -710,6 +710,11 @@ public class NewActionServiceImpl extends BaseService()); + return result; + } + List parsedDataTypeList = new ArrayList<>(); Stream.of(ActionResultDataType.values()) .parallel() 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..c2ba4f1379 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 @@ -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 = newActionService.createAction(action).block(); + + ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); + executeActionDTO.setActionId(createdAction.getId()); + executeActionDTO.setViewMode(false); + + executeAndAssertAction(executeActionDTO, actionConfiguration, mockResult, new ArrayList<>()); + } } From 7f378731ce60de8549900dba65bed264f3749d47 Mon Sep 17 00:00:00 2001 From: johyunkim Date: Wed, 14 Apr 2021 11:31:49 +0900 Subject: [PATCH 03/32] Modify curl --data-raw option to --data (#3973) --- deploy/install.sh | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) 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": { From 5b48a9cc7897a7547cd0edfb9fb1b849afcd536e Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 14 Apr 2021 08:03:56 +0530 Subject: [PATCH 04/32] Fix build failure on maven 3.8 (#3954) Maven 3.8 now blocks HTTP repositories and only allows HTTPS. However, the version redshift jdbc dependency we are using connects to a separate repository that runs on HTTP. The latest version has switched to using HTTPS instead. So this PR upgrades redshift dependency from version 2.0.0.1 to 2.0.0.4. Ref: https://maven.apache.org/docs/3.8.1/release-notes.html#cve-2021-26291 --- app/server/appsmith-plugins/redshiftPlugin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From a259dd00eda3158c7ffa9162f8135a7ac7c7bada Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 14 Apr 2021 10:44:43 +0530 Subject: [PATCH 05/32] Fix internal server error on invalid header in curl command (#3931) * Fix internal server error on invalid header in curl command * Add tests for invalid header/method --- .../server/exceptions/AppsmithError.java | 3 ++- .../server/services/CurlImporterService.java | 3 +++ .../services/CurlImporterServiceTest.java | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) 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..ebdd114f19 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 @@ -81,9 +81,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/CurlImporterService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CurlImporterService.java index 3401ecd31f..2f04ff0aa1 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 @@ -290,6 +290,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/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() { From 3a52f51af0ff62e5f30565a41d4eb6f0d7264c2a Mon Sep 17 00:00:00 2001 From: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Date: Wed, 14 Apr 2021 13:44:06 +0530 Subject: [PATCH 06/32] WIP : Updated test for page spec (#3749) * Updated test for page spec * Updated test for pages with widget * updated test Co-authored-by: nandan.anantharamu --- .../ClientSideTests/Pages/Hide_Page_spec.js | 18 ++++++++++--- .../ClientSideTests/Pages/Page_Load_Spec.js | 25 +++++++++++++++++++ app/client/cypress/locators/Pages.json | 3 ++- 3 files changed, 42 insertions(+), 4 deletions(-) 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/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 From 9b49308abeb5a4be57d246d0ba84f9e93765c4b9 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Thu, 15 Apr 2021 11:36:41 +0530 Subject: [PATCH 07/32] Adding dependency relationship to plugins (#3997) * Added dependencies to be added to dependencyMap on client to Postgres, MSSQL, MySQL and Mongo plugins * Added Dependency config for API * Fixed the test case --- .../formConfig/ApiDependencyConfigs.ts | 9 +++++++++ .../mongoPlugin/src/main/resources/dependency.json | 5 +++++ .../mssqlPlugin/src/main/resources/dependency.json | 5 +++++ .../mysqlPlugin/src/main/resources/dependency.json | 5 +++++ .../src/main/resources/dependency.json | 5 +++++ .../appsmith/server/services/PluginServiceImpl.java | 13 ++++++++++++- .../appsmith/server/services/PluginServiceTest.java | 11 +++++++++++ 7 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 app/client/src/constants/AppsmithActionConstants/formConfig/ApiDependencyConfigs.ts create mode 100644 app/server/appsmith-plugins/mongoPlugin/src/main/resources/dependency.json create mode 100644 app/server/appsmith-plugins/mssqlPlugin/src/main/resources/dependency.json create mode 100644 app/server/appsmith-plugins/mysqlPlugin/src/main/resources/dependency.json create mode 100644 app/server/appsmith-plugins/postgresPlugin/src/main/resources/dependency.json 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/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/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/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/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-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/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) From 9e6e8e5e4b8e4c5d80878d4d78774b7636716f41 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Thu, 15 Apr 2021 12:14:39 +0530 Subject: [PATCH 08/32] Safe handling of alias (#4004) * Safe handling of alias * Unnecessary import --- .../annotations/documenttype/DocumentTypeMapper.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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; } From 6ea795e8a1e5fc0e57a56af4a60789bf856deb87 Mon Sep 17 00:00:00 2001 From: Abhijeet <41686026+abhvsn@users.noreply.github.com> Date: Thu, 15 Apr 2021 16:46:46 +0530 Subject: [PATCH 09/32] Autoscroll to invited organisation (#3945) * Added organization slug to the invite Url, which is finally used by email template(#2359) * Separate method to create email params. Welcome email url variable updated so as to be consistent with other email templates * Unused enum emailType removed, importing of java.util.* removed as per suggestion --- .../appsmith/server/services/UserService.java | 5 ++ .../server/services/UserServiceImpl.java | 48 +++++++++++++------ .../resources/email/welcomeUserTemplate.html | 2 +- .../server/services/UserServiceTest.java | 38 +++++++++++++++ 4 files changed, 77 insertions(+), 16 deletions(-) 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/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/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() { From 812e73a881997f4dd2e2594da44e018c48773fff Mon Sep 17 00:00:00 2001 From: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Date: Fri, 16 Apr 2021 16:08:41 +0530 Subject: [PATCH 10/32] Basic DS test scenarios (#4026) --- .../QueryPane/EmptyDataSource_spec.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/QueryPane/EmptyDataSource_spec.js 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.]", + ); + }); +}); From 4f8cf7904147ae8e42c7545cbe94d40c339c09c5 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Fri, 16 Apr 2021 16:13:03 +0530 Subject: [PATCH 11/32] [Bug fix] : Disallowing action creation with existing names from the page (#4015) --- .../server/exceptions/AppsmithError.java | 1 + .../services/ActionCollectionServiceImpl.java | 6 +- .../services/ApplicationPageServiceImpl.java | 2 +- .../server/services/CurlImporterService.java | 8 +- .../server/services/ItemServiceImpl.java | 7 +- .../server/services/LayoutActionService.java | 5 + .../services/LayoutActionServiceImpl.java | 77 +++++++++++++++ .../server/services/NewActionService.java | 10 +- .../server/services/NewActionServiceImpl.java | 94 ++++--------------- .../solutions/ExamplesOrganizationCloner.java | 4 +- .../server/services/ActionServiceTest.java | 64 ++++++------- .../services/ApplicationServiceTest.java | 12 ++- .../services/DatasourceServiceTest.java | 5 +- .../services/LayoutActionServiceTest.java | 51 ++++++++-- .../server/services/LayoutServiceTest.java | 22 ++--- .../server/services/PageServiceTest.java | 2 +- .../ShareOrganizationPermissionTests.java | 10 +- 17 files changed, 229 insertions(+), 151 deletions(-) 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 ebdd114f19..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), 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 2f04ff0aa1..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 { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java index 33f3f6c91a..2c83ce2d6d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ItemServiceImpl.java @@ -23,16 +23,19 @@ public class ItemServiceImpl implements ItemService { private final PluginService pluginService; private final MarketplaceService marketplaceService; private final NewActionService newActionService; + private final LayoutActionService layoutActionService; private static final String RAPID_API_PLUGIN = "rapidapi-plugin"; public ItemServiceImpl(ApiTemplateService apiTemplateService, PluginService pluginService, MarketplaceService marketplaceService, - NewActionService newActionService) { + NewActionService newActionService, + LayoutActionService layoutActionService) { this.apiTemplateService = apiTemplateService; this.pluginService = pluginService; this.marketplaceService = marketplaceService; this.newActionService = newActionService; + this.layoutActionService = layoutActionService; } @Override @@ -108,6 +111,6 @@ public class ItemServiceImpl implements ItemService { action.setPluginType(plugin.getType()); return action; }) - .flatMap(newActionService::createAction); + .flatMap(layoutActionService::createAction); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java index a0a124b24c..c7c8db9093 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java @@ -1,5 +1,6 @@ package com.appsmith.server.services; +import com.appsmith.external.helpers.AppsmithEventContext; import com.appsmith.server.domains.Layout; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; @@ -22,4 +23,8 @@ public interface LayoutActionService { 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..c105ac36e9 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; @@ -46,6 +50,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 @@ -290,6 +295,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 +754,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..7f0f3539ad 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); 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 bac9d46174..8497ee4ada 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,7 @@ public class NewActionServiceImpl extends BaseService>>() {}); + objectMapper.readValue(body, new TypeReference>>() { + }); return new ParsedDataType(ActionResultDataType.TABLE); } catch (JsonProcessingException e) { return null; @@ -765,7 +709,7 @@ public class NewActionServiceImpl extends BaseService 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/test/java/com/appsmith/server/services/ActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionServiceTest.java index c2ba4f1379..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()); @@ -1328,7 +1328,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()); 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/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..7bb73e40cd 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 @@ -17,6 +17,8 @@ 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.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.OrganizationRepository; @@ -180,7 +182,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 +192,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,7 +239,7 @@ 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(); @@ -282,7 +284,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,7 +295,7 @@ 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()); @@ -346,8 +348,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 +434,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 +500,37 @@ 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 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/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()); From eaa24ca894cea79f6c3c329160bd62060c59f821 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Mon, 19 Apr 2021 12:35:10 +0530 Subject: [PATCH 12/32] [Bug Fix] : Fetch actions by id during refactor action name to solve IncorrectResultSizeDataAccessException for pages where duplicate action names exist (#4030) * New dto for refactoring action name which includes actionId as well * Refactor action name now also takes actionId as payload and fetches the action from db by id instead of name to solve for the scenario where the action names could be duplicated. * Adding actionId to QP of action refactor API. * WIP : test for asserting the bug fix * WIP test * Fixed the test case asserting refactoring works for an action when there are duplicate action names in the page. Co-authored-by: Ashok Kumar M <35134347+marks0351@users.noreply.github.com> --- app/client/src/api/ActionAPI.tsx | 1 + app/client/src/sagas/ActionSagas.ts | 1 + .../server/controllers/ActionController.java | 8 +- .../server/dtos/RefactorActionNameDTO.java | 14 +++ .../server/services/LayoutActionService.java | 3 +- .../services/LayoutActionServiceImpl.java | 16 +-- .../server/services/NewActionService.java | 2 + .../server/services/NewActionServiceImpl.java | 6 + .../services/LayoutActionServiceTest.java | 109 +++++++++++++++--- 9 files changed, 135 insertions(+), 25 deletions(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/RefactorActionNameDTO.java diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index 99cc5e15b1..d82c6e7291 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; diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 4907336c59..e7ccadc2b9 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -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/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/services/LayoutActionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java index c7c8db9093..31ca570a81 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java @@ -4,6 +4,7 @@ import com.appsmith.external.helpers.AppsmithEventContext; import com.appsmith.server.domains.Layout; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; +import com.appsmith.server.dtos.RefactorActionNameDTO; import com.appsmith.server.dtos.RefactorNameDTO; import com.appsmith.server.dtos.LayoutDTO; import net.minidev.json.JSONObject; @@ -16,7 +17,7 @@ public interface LayoutActionService { Mono refactorWidgetName(RefactorNameDTO refactorNameDTO); - Mono refactorActionName(RefactorNameDTO refactorNameDTO); + Mono refactorActionName(RefactorActionNameDTO refactorActionNameDTO); Mono updateAction(String id, ActionDTO action); 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 c105ac36e9..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 @@ -18,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; @@ -159,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)); } 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 7f0f3539ad..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 @@ -37,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 8497ee4ada..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 @@ -851,6 +851,12 @@ 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/test/java/com/appsmith/server/services/LayoutActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java index 7bb73e40cd..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,11 +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; @@ -89,6 +90,9 @@ public class LayoutActionServiceTest { @Autowired NewPageService newPageService; + @Autowired + NewActionRepository actionRepository; + Application testApp = null; PageDTO testPage = null; @@ -244,13 +248,14 @@ public class LayoutActionServiceTest { 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); @@ -297,13 +302,14 @@ public class LayoutActionServiceTest { action.setId(null); 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); @@ -502,6 +508,83 @@ public class LayoutActionServiceTest { .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() { From 1c1027994584f13874ad3428f98a32fabd1d42cb Mon Sep 17 00:00:00 2001 From: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:31:05 +0530 Subject: [PATCH 13/32] Updated profile tests (#4002) --- .../cypress/fixtures/resetPassword.json | 7 +++++ .../UserProfile/UpdateUsersName_spec.js | 31 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 app/client/cypress/fixtures/resetPassword.json 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/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, + ); + }); }); From d1a13a28bcb6852b52b05cf9ff9b25d1dfd67428 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Mon, 19 Apr 2021 14:51:04 +0530 Subject: [PATCH 14/32] Remove coverage drop failure checks (#4048) --- .github/workflows/client-build.yml | 2 -- 1 file changed, 2 deletions(-) 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 From 59ad172d81ec023494df9e1461b4879f61d34724 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Mon, 19 Apr 2021 18:47:27 +0530 Subject: [PATCH 15/32] Hot fix redirection race in oAuth autorisation flow (#4052) --- app/client/src/actions/datasourceActions.ts | 15 +++++++++++++++ app/client/src/sagas/DatasourcesSagas.ts | 15 +++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) 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/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( From f509f1b50bfccc6ef0c79f2ed0ed5c84c9d591ec Mon Sep 17 00:00:00 2001 From: Apeksha Bhosale <7846888+ApekshaBhosale@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:12:25 +0530 Subject: [PATCH 16/32] datasource edit should be always enabled (#3979) --- .../editorComponents/form/fields/EmbeddedDatasourcePathField.tsx | 1 + 1 file changed, 1 insertion(+) 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( From 568269e8e1f4d6fa14e3f47ced389658dde284f7 Mon Sep 17 00:00:00 2001 From: Apeksha Bhosale <7846888+ApekshaBhosale@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:13:26 +0530 Subject: [PATCH 17/32] size change for raw and editor in API editor (#3984) * size change for raw and editor in API editor * removed from global css file * removed classes --- app/client/src/index.css | 1 - app/client/src/pages/Editor/APIEditor/PostBodyData.tsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) 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/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; From 270fdf5401407ef8d656f0a46f402050bcc8c76a Mon Sep 17 00:00:00 2001 From: Vihar Kurama Date: Tue, 20 Apr 2021 11:15:21 +0530 Subject: [PATCH 18/32] Update links and add GraphQL tutorial (#4027) --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 From 256f64aefc21536ae647461b89ee8d9d0e5aee74 Mon Sep 17 00:00:00 2001 From: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Date: Tue, 20 Apr 2021 11:28:30 +0530 Subject: [PATCH 19/32] Removed test for collapse/open Property pane (#4059) --- .../DisplayWidgets/Table_PropertyPane_spec.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) 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"); From 333c4169eb82fecf0a87db506a8e26ece4d66239 Mon Sep 17 00:00:00 2001 From: Apeksha Bhosale <7846888+ApekshaBhosale@users.noreply.github.com> Date: Tue, 20 Apr 2021 12:26:30 +0530 Subject: [PATCH 20/32] name can't be read from empty object (#3958) --- app/client/src/pages/Editor/PropertyPaneTitle.tsx | 6 +++++- app/client/src/sagas/ApiPaneSagas.ts | 4 ++-- app/client/src/sagas/QueryPaneSagas.ts | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/client/src/pages/Editor/PropertyPaneTitle.tsx b/app/client/src/pages/Editor/PropertyPaneTitle.tsx index 5543c7c982..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()); } }, 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/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, From f1253074a80dde27041be9908a8f2043f9e1da5c Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Tue, 20 Apr 2021 13:13:22 +0530 Subject: [PATCH 21/32] Added null check for NPE handling in sanitize datasource (#4072) --- .../com/appsmith/server/services/DatasourceServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 Date: Tue, 20 Apr 2021 13:58:59 +0530 Subject: [PATCH 22/32] Reverting use of Mongo Listener since we aren't applying this yet (#4073) * Added synchronization to encryption map * Reverting listener for now --- .../annotations/encryption/EncryptionHandler.java | 5 ++++- .../com/appsmith/server/configurations/MongoConfig.java | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) 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-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); +// } } From 302181bf487944b96cab110047f22ee3d008f4e1 Mon Sep 17 00:00:00 2001 From: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Date: Tue, 20 Apr 2021 14:29:52 +0530 Subject: [PATCH 23/32] WIP: Added new test for Text widget (#3953) * Added new test for Text widget * Added tests for text widget property panes * updated tests --- app/client/cypress/fixtures/example.json | 1 + app/client/cypress/fixtures/textDsl.json | 45 ++++++++ .../DisplayWidgets/Text_new_feature_spec.js | 109 ++++++++++++++++++ app/client/cypress/support/commands.js | 8 ++ 4 files changed, 163 insertions(+) create mode 100644 app/client/cypress/fixtures/textDsl.json create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_new_feature_spec.js 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/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/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/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() From ff3c5cf37f5dacb56b006a65cb8d0262bfba1938 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Apr 2021 15:59:30 +0530 Subject: [PATCH 24/32] Bump ssri from 6.0.1 to 6.0.2 in /app/client (#4060) Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2. - [Release notes](https://github.com/npm/ssri/releases) - [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md) - [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/client/yarn.lock | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) 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" From eeaa26a7ccb907dc7814dfb3aee9135b467c5f9a Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Tue, 20 Apr 2021 16:09:48 +0530 Subject: [PATCH 25/32] Type migration from String to Object for plugin specified templates to preserve boolean and any other future data types (#4053) * WIP : Type migration from String to Object for value * Migrating config from string to boolean for prepared statement. Handled error for already stored actions where PS config is stored as String and not Boolean. --- .../formConfig/ApiSettingsConfig.ts | 2 +- .../appsmith/external/models/Property.java | 4 +-- .../com/external/plugins/AmazonS3Plugin.java | 32 +++++++++---------- .../external/plugins/ElasticSearchPlugin.java | 2 +- .../com/external/plugins/FirestorePlugin.java | 21 ++++++------ .../com/external/plugins/MongoPlugin.java | 9 +++++- .../src/main/resources/setting.json | 2 +- .../com/external/plugins/MssqlPlugin.java | 9 +++++- .../src/main/resources/setting.json | 2 +- .../com/external/plugins/MySqlPlugin.java | 9 +++++- .../src/main/resources/setting.json | 2 +- .../com/external/plugins/PostgresPlugin.java | 9 +++++- .../src/main/resources/setting.json | 2 +- .../com/external/plugins/RapidApiPlugin.java | 14 ++++---- .../com/external/plugins/RestApiPlugin.java | 23 ++++++++----- 15 files changed, 88 insertions(+), 54 deletions(-) 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/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/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/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/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/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/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)); } From 318cb78464ee7db25fd5254a6a2e5121d686eced Mon Sep 17 00:00:00 2001 From: Vicky Bansal <67091118+vicky-primathon@users.noreply.github.com> Date: Tue, 20 Apr 2021 16:17:06 +0530 Subject: [PATCH 26/32] Escape line break characters when exporting table data to CSV (#3757) --- .../TableComponent/CommonUtilities.test.ts | 89 +++++++++++++++++- .../TableComponent/CommonUtilities.ts | 45 ++++++++- .../appsmith/TableComponent/Constants.ts | 4 +- .../TableComponent/TableDataDownload.tsx | 92 ++++++++----------- 4 files changed, 172 insertions(+), 58 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/CommonUtilities.test.ts b/app/client/src/components/designSystems/appsmith/TableComponent/CommonUtilities.test.ts index 61c8a10b96..026291ff16 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/CommonUtilities.test.ts +++ b/app/client/src/components/designSystems/appsmith/TableComponent/CommonUtilities.test.ts @@ -1,5 +1,11 @@ -import { sortTableFunction } from "components/designSystems/appsmith/TableComponent/CommonUtilities"; -import { ColumnTypes } from "components/designSystems/appsmith/TableComponent/Constants"; +import { + sortTableFunction, + transformTableDataIntoCsv, +} from "components/designSystems/appsmith/TableComponent/CommonUtilities"; +import { + ColumnTypes, + TableColumnProps, +} from "components/designSystems/appsmith/TableComponent/Constants"; describe("TableUtilities", () => { 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); }; From 30a82c2d249267c7a9d43211330a7df01ca0f727 Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Tue, 20 Apr 2021 18:54:34 +0530 Subject: [PATCH 27/32] Hotfix for broken release. Would do a proper fix on release tomorrow with migration (#4075) --- .../mongoPlugin/src/main/resources/editor.json | 14 ++++++++++---- .../mssqlPlugin/src/main/resources/editor.json | 14 ++++++++++---- .../mysqlPlugin/src/main/resources/editor.json | 14 ++++++++++---- .../postgresPlugin/src/main/resources/editor.json | 14 ++++++++++---- 4 files changed, 40 insertions(+), 16 deletions(-) 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/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/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/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 + ] } } ] From 6c681ef98c04b44fa5b8dc2907f52dc7ffc81f2e Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Wed, 21 Apr 2021 13:18:24 +0530 Subject: [PATCH 28/32] Fix initial value check for action form config (#4080) --- app/client/src/api/ActionAPI.tsx | 32 ++----------------- .../src/components/formControls/utils.test.ts | 20 ++++++++++++ .../src/components/formControls/utils.ts | 2 +- app/client/src/sagas/ActionSagas.ts | 6 ++-- 4 files changed, 26 insertions(+), 34 deletions(-) diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index d82c6e7291..03d6f54900 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -108,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); @@ -136,7 +132,7 @@ class ActionAPI extends API { return API.get(ActionAPI.url, { pageId }); } - static updateAPI( + static updateAction( apiConfig: Partial, ): AxiosPromise { if (ActionAPI.apiUpdateCancelTokenSource) { @@ -159,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, @@ -192,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/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/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index e7ccadc2b9..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); From dc7d0023b37358a2e96213bd36d8f9d8f0aed94d Mon Sep 17 00:00:00 2001 From: Nikhil Nandagopal Date: Wed, 21 Apr 2021 14:25:34 +0530 Subject: [PATCH 29/32] Update ---epic.md --- .github/ISSUE_TEMPLATE/---epic.md | 43 ++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 12 deletions(-) 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 | From f7794495d7a48c405bbdd21f046fe9a6128f9af6 Mon Sep 17 00:00:00 2001 From: Satish Gandham Date: Wed, 21 Apr 2021 14:55:35 +0530 Subject: [PATCH 30/32] Fix Map Widget crash #3843 (#4031) * Fix map method being called on undefined varaible in MapComponent #3843 Sentry Issue: APPSMITH-N5 * Add type to marker property #3843 Co-authored-by: Satish Gandham --- .../designSystems/appsmith/MapComponent.tsx | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) 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 && ( Date: Wed, 21 Apr 2021 19:15:02 +0530 Subject: [PATCH 31/32] Set explicit Java version for CI (#4091) * Set explicit Java version for CI * Trigger Co-authored-by: Nidhi --- .github/workflows/external-client-test.yml | 2 +- .github/workflows/server.yml | 2 +- app/server/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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. From 4d73e7e0b3b425b5ae380d546f82b109af5bea62 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Wed, 21 Apr 2021 20:04:25 +0530 Subject: [PATCH 32/32] Widget validation for nested property paths (#3947) --- .../constants/PropertyControlConstants.tsx | 2 + app/client/src/constants/WidgetValidation.ts | 57 ++-- .../src/entities/DataTree/dataTreeFactory.ts | 2 + .../entities/DataTree/dataTreeWidget.test.ts | 245 ++++++++++++++++++ .../src/entities/DataTree/dataTreeWidget.ts | 7 +- app/client/src/entities/Widget/utils.test.ts | 14 + app/client/src/entities/Widget/utils.ts | 17 +- .../mockResponses/WidgetConfigResponse.tsx | 10 - .../entityReducers/widgetConfigReducer.tsx | 2 - app/client/src/sagas/EvaluationsSaga.ts | 7 +- app/client/src/sagas/WidgetOperationSagas.tsx | 1 - app/client/src/selectors/editorSelectors.tsx | 1 + app/client/src/utils/WidgetFactory.tsx | 23 -- app/client/src/utils/WidgetRegistry.tsx | 25 -- app/client/src/utils/WidgetValidation.ts | 16 -- .../dataTreeTypeDefCreator.test.ts | 1 + app/client/src/widgets/AlertWidget.tsx | 20 -- app/client/src/widgets/BaseWidget.tsx | 9 - app/client/src/widgets/ButtonWidget.tsx | 16 +- app/client/src/widgets/ChartWidget/index.tsx | 13 - .../src/widgets/ChartWidget/propertyConfig.ts | 8 + app/client/src/widgets/CheckboxWidget.tsx | 17 +- app/client/src/widgets/ContainerWidget.tsx | 3 + app/client/src/widgets/DatePickerWidget.tsx | 27 +- app/client/src/widgets/DatePickerWidget2.tsx | 28 +- app/client/src/widgets/DropdownWidget.tsx | 24 +- app/client/src/widgets/FilepickerWidget.tsx | 21 +- app/client/src/widgets/FormButtonWidget.tsx | 18 +- app/client/src/widgets/FormWidget.tsx | 3 + app/client/src/widgets/ImageWidget.tsx | 17 +- app/client/src/widgets/InputWidget.tsx | 35 +-- app/client/src/widgets/MapWidget.tsx | 17 +- app/client/src/widgets/RadioGroupWidget.tsx | 20 +- .../src/widgets/RichTextEditorWidget.tsx | 12 +- app/client/src/widgets/SwitchWidget.tsx | 16 +- .../TableWidget/TablePropertyPaneConfig.ts | 5 + app/client/src/widgets/TableWidget/index.tsx | 21 -- app/client/src/widgets/TabsWidget.tsx | 10 +- app/client/src/widgets/TextWidget.tsx | 14 +- app/client/src/widgets/VideoWidget.tsx | 13 +- app/client/src/workers/DataTreeEvaluator.ts | 74 +++--- app/client/src/workers/evaluate.test.ts | 1 + app/client/src/workers/evaluation.test.ts | 210 +-------------- app/client/src/workers/evaluation.worker.ts | 18 +- app/client/src/workers/evaluationUtils.ts | 97 +++---- app/client/src/workers/validations.ts | 3 +- 46 files changed, 507 insertions(+), 713 deletions(-) create mode 100644 app/client/src/entities/DataTree/dataTreeWidget.test.ts delete mode 100644 app/client/src/utils/WidgetValidation.ts delete mode 100644 app/client/src/widgets/AlertWidget.tsx 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/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/reducers/entityReducers/widgetConfigReducer.tsx b/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx index 65e7d5a939..155fae76c6 100644 --- a/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx +++ b/app/client/src/reducers/entityReducers/widgetConfigReducer.tsx @@ -14,7 +14,6 @@ import { TableWidgetProps } from "../../widgets/TableWidget/TableWidgetConstants import { DropdownWidgetProps } from "../../widgets/DropdownWidget"; import { CheckboxWidgetProps } from "../../widgets/CheckboxWidget"; import { RadioGroupWidgetProps } from "../../widgets/RadioGroupWidget"; -import { AlertWidgetProps } from "../../widgets/AlertWidget"; import { FilePickerWidgetProps } from "../../widgets/FilepickerWidget"; import { TabsWidgetProps, @@ -67,7 +66,6 @@ export interface WidgetConfigReducerState { CHECKBOX_WIDGET: Partial & 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/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/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 2fbc092f94..f8a7a57cff 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) {