From 1e6a0e8e9248cb918f8580407475adb2a286928b Mon Sep 17 00:00:00 2001 From: "vicky-primathon.in" Date: Tue, 13 Apr 2021 16:55:50 +0530 Subject: [PATCH 01/62] Fix filepicker widget delete file not working Added cypress test case to test selected file deletion works --- app/client/cypress/fixtures/testFile2.mov | Bin 0 -> 256000 bytes .../FormWidgets/FilePicker_spec.js | 23 ++++++++++++++++++ app/client/src/widgets/FilepickerWidget.tsx | 3 +-- 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 app/client/cypress/fixtures/testFile2.mov diff --git a/app/client/cypress/fixtures/testFile2.mov b/app/client/cypress/fixtures/testFile2.mov new file mode 100644 index 0000000000000000000000000000000000000000..af5b36c70550177e32f0f81a6ee70e065e9caf8f GIT binary patch literal 256000 zcmeIuF#!Mo0K%a4Pi+Wah(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd v0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM82APT=q&&N literal 0 HcmV?d00001 diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FilePicker_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FilePicker_spec.js index 1a71d138c1..3330821242 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FilePicker_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FilePicker_spec.js @@ -43,6 +43,29 @@ describe("FilePicker Widget Functionality", function() { cy.get("button").contains("1 files selected"); }); + it("It checks the deletion of filepicker works as expected", function() { + cy.get(commonlocators.filePickerButton).click(); + cy.get(commonlocators.filePickerInput) + .first() + .attachFile("testFile.mov"); + cy.get(commonlocators.filePickerUploadButton).click(); + //eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(500); + cy.get("button").contains("1 files selected"); + cy.get(commonlocators.filePickerButton).click(); + //eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(200); + cy.get("button.uppy-Dashboard-Item-action--remove").click(); + cy.get("button.uppy-Dashboard-browse").click(); + cy.get(commonlocators.filePickerInput) + .first() + .attachFile("testFile2.mov"); + cy.get(commonlocators.filePickerUploadButton).click(); + //eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(500); + cy.get("button").contains("1 files selected"); + }); + afterEach(() => { // put your clean up code if any }); diff --git a/app/client/src/widgets/FilepickerWidget.tsx b/app/client/src/widgets/FilepickerWidget.tsx index 2fbc092f94..e3ed4694b3 100644 --- a/app/client/src/widgets/FilepickerWidget.tsx +++ b/app/client/src/widgets/FilepickerWidget.tsx @@ -326,14 +326,13 @@ class FilePickerWidget extends BaseWidget< return file.id !== dslFile.id; }) : []; - this.props.updateWidgetMetaProperty("files", updatedFiles); + this.props.updateWidgetMetaProperty("selectedFiles", updatedFiles); }); this.state.uppy.on("files-added", (files: any[]) => { const dslFiles = this.props.selectedFiles ? [...this.props.selectedFiles] : []; - const fileReaderPromises = files.map((file) => { const reader = new FileReader(); return new Promise((resolve) => { 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 02/62] 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 03/62] 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 04/62] 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 05/62] 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 06/62] 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 07/62] 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 08/62] 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 09/62] 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 10/62] 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 11/62] 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 12/62] 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 13/62] 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 14/62] 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 15/62] 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 16/62] 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 16:10:45 +0530 Subject: [PATCH 17/62] updated the default table data to guide users to connect a datasource --- .../mockResponses/WidgetConfigResponse.tsx | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 03cf9ab4b5..f9e50de0a9 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -138,28 +138,36 @@ const WidgetConfigResponse: WidgetConfigReducerState = { horizontalAlignment: "LEFT", verticalAlignment: "CENTER", primaryColumns: {}, - derivedColumns: {}, + derivedColumns: { + action: { + id: "1", + label: "Action", + columnType: "button", + isVisible: true, + isDerived: true, + index: 3, + buttonLabel: "Start", + width: 50, + computedValue: "Do It", + onClick: + "{{currentRow.step === '#1' ? showAlert('Done', 'success') : currentRow.step === '#2' ? navigateTo('https://docs.appsmith.com/core-concepts/connecting-to-data-sources/connecting-to-databases/querying-a-database',undefined,'NEW_WINDOW') : navigateTo('https://docs.appsmith.com/core-concepts/displaying-data-read/display-data-tables',undefined,'NEW_WINDOW')}}", + }, + }, tableData: [ { - id: 2381224, - email: "michael.lawson@reqres.in", - userName: "Michael Lawson", - productName: "Chicken Sandwich", - orderAmount: 4.99, + step: "#1", + task: "Drag a Table", + status: "✅", }, { - id: 2736212, - email: "lindsay.ferguson@reqres.in", - userName: "Lindsay Ferguson", - productName: "Tuna Salad", - orderAmount: 9.99, + step: "#2", + task: "Create a Query fetch_users with the Mock DB", + status: "--", }, { - id: 6788734, - email: "tobias.funke@reqres.in", - userName: "Tobias Funke", - productName: "Beef steak", - orderAmount: 19.99, + step: "#3", + task: "Bind the query to the table {{fetch_users.data}}", + status: "--", }, ], version: 1, From 14ceab809a820e5426dd1d32d804389982feca87 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 21 Apr 2021 19:15:02 +0530 Subject: [PATCH 18/62] 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 19/62] 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) { From 6b31aa333b162711a7c6df129825af19d84e815f Mon Sep 17 00:00:00 2001 From: Nidhi Date: Thu, 22 Apr 2021 09:00:09 +0530 Subject: [PATCH 20/62] Introducing Google Sheets Plugin (#3517) * cherry pick -make new * revert to enable fix from release * attempt to hook into existing datasource editor * gSheets plugin skeleton from Rest API * Changes for database migration * fix for auth code * separate it out * action page loads! * add to explorer * create action from datasource * Editor JSON WIP * working query form * Editor JSON WIP * import to * fix toast message * redirect from datasource and editor pages * fix onboarding * fix imports and constants * refactor form out * refactor queryForm * Merge branch 'release' into feature/google-sheets * Merge branch 'release' into feature/google-sheets * initial values from settings, editor and form * Check * remove dangling code around lightTheme * Safety net * remove class * try mouseover solve * force click * changes from review * fix action form name on import * Merge branch 'release' into feature/google-sheets * minor cleanup * Merge branch 'release' into feature/google-sheets * WIP * Google sheets changes * Merge conflicts * Merging and fixes, needs refactoring * Check * Merge branch 'release' into feature/google-sheets * Fixed tests * Add cloud services env variable * Clean up saga * Clean up * Refactoring * Deleted svg file * Minor fixes * Modified design to allow behaviour in google sheets methods (#3486) * Modified design to allow behaviour in google sheets methods * Review changes * Removed sysout * Added handling of edge cases with table data * Merge branch 'release' into feature/google-sheets * Fixes * Fixes * Added validations * Improved tests * Removed extraneous injected bean * Review changes * Fixed bug with method * Changes to Google sheets plugin's request and response structures (#3692) * Method changes * Removed logging * Renaming options * Reverting pom version * Modified type of collection variables, fixed errors * Converted row offset field to one that supports dynamic bindings * Review changes * List SAAS plugin type actions under lightning menu apis (#3820) * list saas plugin type actions under lightning menu apis * combine saas plugin type actions in the other sub menu of lightning menu Co-authored-by: Hetu Nandu * Fix merge issues * Prettified query editor and a few fixes w/ ux * Test fixes * Reformatting request * code for REST added (#3876) Co-authored-by: hetunandu * Renamed body to row object * Renamed placeholder for range * Renamed range heading * Modifications to handle range semantics * Use spreadsheet Url instead of id * Ordering of methods * Removed logging * Add tests for Dynamic text controls * Add tests for url helpers * Fix coverage config * Nevermind * Interface changes * There is no body here * Yay to hints * Delete row field is separately handled as row index * placeholder support (#4001) * Fixed tests, typos and creating new sheets with random rows * Switched to using 'rowIndex' throughout * binding path added for query input field (#4016) * - Fixed QA bugs (#4032) - Split delete sheet into two - Removed dynamic query input types from hidden keys * Proper exceptions * Removed extra logging * Throw exception if update method does not match any of the columns * Same for bulk update * Zero-indexed delete row * I'm a space bound rocket ship * Logic to register installations with cs (#4062) * Logic to register installations with cs * Clean up * Casting to string * Checking to see if this makes the test pass * Added an extra null check Co-authored-by: Piyush Co-authored-by: hetunandu Co-authored-by: Hetu Nandu Co-authored-by: Apeksha Bhosale <7846888+ApekshaBhosale@users.noreply.github.com> --- .env.example | 2 + .../BindApi_withPageload_Input_spec.js | 2 - .../DisplayWidgets/Modal_spec.js | 1 - app/client/cypress/support/commands.js | 2 +- .../docker/templates/nginx-app.conf.template | 1 + app/client/package.json | 3 +- app/client/public/index.html | 1 + app/client/src/actions/datasourceActions.ts | 28 +- app/client/src/actions/utilActions.ts | 15 + app/client/src/api/CloudServicesApi.ts | 6 + app/client/src/api/PluginApi.ts | 3 +- app/client/src/api/SaasApi.ts | 25 + .../editorComponents/ActionCreator.tsx | 7 +- .../editorComponents/LightningMenu/hooks.ts | 12 +- .../editorComponents/LightningMenu/index.tsx | 4 +- .../DynamicInputTextControl.test.tsx | 46 ++ .../formControls/DynamicInputTextControl.tsx | 16 +- .../DynamicTextFieldControl.test.tsx | 70 ++ .../formControls/DynamicTextFieldControl.tsx | 34 +- .../src/components/formControls/utils.test.ts | 11 + .../src/components/formControls/utils.ts | 12 + app/client/src/configs/index.ts | 5 + app/client/src/configs/types.ts | 2 + .../src/constants/ApiEditorConstants.ts | 1 - .../ActionConstants.tsx | 3 + .../formConfig/GoogleSheetsSettingsConfig.ts | 27 + .../src/constants/QueryEditorConstants.ts | 2 - .../src/constants/ReduxActionConstants.tsx | 4 +- app/client/src/constants/forms.ts | 2 + app/client/src/constants/messages.ts | 7 + app/client/src/entities/Action/index.ts | 8 +- .../pages/Editor/APIEditor/ApiHomeScreen.tsx | 30 + .../src/pages/Editor/APIEditor/index.tsx | 15 +- .../pages/Editor/DataSourceEditor/DBForm.tsx | 311 +-------- .../DataSourceEditor/DatasourceHome.tsx | 8 +- .../Editor/DataSourceEditor/FormTitle.tsx | 6 +- .../Editor/DataSourceEditor/JSONtoForm.tsx | 285 +++++++++ .../RestAPIDatasourceForm.tsx | 23 +- .../pages/Editor/DataSourceEditor/index.tsx | 31 +- .../Editor/Explorer/Actions/ActionsGroup.tsx | 2 + .../pages/Editor/Explorer/Actions/helpers.tsx | 63 +- .../Explorer/Datasources/DatasourceEntity.tsx | 22 +- .../Explorer/Onboarding/DBQueryGroup.tsx | 4 +- .../Explorer/PluginGroup/PluginGroup.tsx | 2 +- .../src/pages/Editor/Explorer/helpers.test.ts | 32 + .../src/pages/Editor/Explorer/helpers.tsx | 19 + .../Editor/QueryEditor/EditorJSONtoForm.tsx | 580 +++++++++++++++++ .../src/pages/Editor/QueryEditor/Form.tsx | 602 +----------------- .../src/pages/Editor/QueryEditor/index.tsx | 13 +- .../pages/Editor/SaaSEditor/ActionForm.tsx | 131 ++++ .../Editor/SaaSEditor/DatasourceCard.tsx | 160 +++++ .../Editor/SaaSEditor/DatasourceForm.tsx | 278 ++++++++ .../src/pages/Editor/SaaSEditor/ListView.tsx | 208 ++++++ .../src/pages/Editor/SaaSEditor/constants.ts | 46 ++ .../src/pages/Editor/SaaSEditor/routes.ts | 23 + app/client/src/pages/Editor/routes.tsx | 5 + app/client/src/pages/common/NotFound.tsx | 56 ++ .../uiReducers/datasourcePaneReducer.ts | 10 - app/client/src/sagas/ActionExecutionSagas.ts | 14 +- app/client/src/sagas/ActionSagas.ts | 54 +- app/client/src/sagas/ApiPaneSagas.ts | 46 +- app/client/src/sagas/DatasourcesSagas.ts | 150 ++++- app/client/src/sagas/QueryPaneSagas.ts | 40 +- app/client/src/sagas/SaaSPaneSagas.ts | 104 +++ app/client/src/sagas/UtilSagas.ts | 23 + app/client/src/sagas/index.tsx | 6 + app/client/src/selectors/editorSelectors.tsx | 2 - app/client/src/selectors/entitiesSelector.ts | 26 +- app/client/src/store.ts | 10 +- app/client/src/utils/AnalyticsUtil.tsx | 3 + .../test/__mocks__/CodeMirrorEditorMock.ts | 17 + app/client/test/testUtils.tsx | 48 +- app/client/yarn.lock | 7 + .../external/exceptions/BaseException.java | 4 +- .../external/models/AuthenticationDTO.java | 13 + .../models/AuthenticationResponse.java | 2 + .../com/appsmith/external/models/OAuth2.java | 14 +- .../googleSheetsPlugin/plugin.properties | 5 + .../googleSheetsPlugin/pom.xml | 260 ++++++++ .../com/external/config/AppendMethod.java | 223 +++++++ .../com/external/config/BulkAppendMethod.java | 239 +++++++ .../com/external/config/BulkUpdateMethod.java | 243 +++++++ .../java/com/external/config/ClearMethod.java | 50 ++ .../java/com/external/config/CopyMethod.java | 48 ++ .../com/external/config/CreateMethod.java | 154 +++++ .../com/external/config/DeleteRowMethod.java | 152 +++++ .../external/config/DeleteSheetMethod.java | 143 +++++ .../com/external/config/GetValuesMethod.java | 238 +++++++ .../config/GoogleSheetsMethodStrategy.java | 42 ++ .../java/com/external/config/InfoMethod.java | 44 ++ .../com/external/config/ListSheetsMethod.java | 64 ++ .../main/java/com/external/config/Method.java | 73 +++ .../com/external/config/MethodConfig.java | 92 +++ .../external/config/MethodIdentifiers.java | 16 + .../com/external/config/UpdateMethod.java | 204 ++++++ .../com/external/constants/GoogleSheets.java | 8 + .../java/com/external/domains/RowObject.java | 107 ++++ .../external/plugins/GoogleSheetsPlugin.java | 187 ++++++ .../java/com/external/utils/JSONUtils.java | 34 + .../java/com/external/utils/SheetsUtil.java | 21 + .../src/main/resources/editor.json | 419 ++++++++++++ .../src/main/resources/form.json | 42 ++ .../external/config/GetValuesMethodTest.java | 165 +++++ .../external/config/ListSheetsMethodTest.java | 73 +++ app/server/appsmith-plugins/pom.xml | 1 + .../appsmith-plugins/rapidApiPlugin/pom.xml | 13 +- .../com/external/plugins/RapidApiPlugin.java | 4 +- .../com/external/plugins/RedshiftPlugin.java | 4 +- .../external/plugins/RedshiftPluginTest.java | 9 +- .../com/external/plugins/RestApiPlugin.java | 13 +- .../OAuth2ClientCredentialsTest.java | 2 +- app/server/appsmith-server/pom.xml | 1 - .../server/configurations/InstanceConfig.java | 80 +++ .../appsmith/server/constants/Appsmith.java | 6 + .../com/appsmith/server/constants/Url.java | 1 + .../controllers/DatasourceController.java | 5 +- .../controllers/RestApiImportController.java | 1 - .../server/controllers/SaasController.java | 46 ++ .../appsmith/server/domains/PluginType.java | 2 +- .../appsmith/server/dtos/IntegrationDTO.java | 40 ++ .../server/exceptions/AppsmithError.java | 3 +- .../server/exceptions/AppsmithException.java | 6 +- .../appsmith/server/exceptions/ErrorDTO.java | 2 + .../exceptions/GlobalExceptionHandler.java | 4 +- .../server/helpers/RedirectHelper.java | 2 +- .../server/migrations/DatabaseChangelog.java | 37 +- .../services/AuthenticationValidator.java | 34 + .../DatasourceContextServiceImpl.java | 4 +- .../services/DatasourceServiceImpl.java | 4 +- .../server/services/LayoutActionService.java | 2 +- .../server/services/NewActionServiceImpl.java | 74 ++- .../server/services/PluginService.java | 3 + .../server/services/PluginServiceImpl.java | 9 + .../solutions/AuthenticationService.java | 273 +++++++- .../DatasourceStructureSolution.java | 4 +- .../server/services/ActionServiceTest.java | 2 +- .../solutions/AuthenticationServiceTest.java | 13 +- app/server/pom.xml | 4 + 138 files changed, 6738 insertions(+), 1226 deletions(-) create mode 100644 app/client/src/actions/utilActions.ts create mode 100644 app/client/src/api/CloudServicesApi.ts create mode 100644 app/client/src/api/SaasApi.ts create mode 100644 app/client/src/components/formControls/DynamicInputTextControl.test.tsx create mode 100644 app/client/src/components/formControls/DynamicTextFieldControl.test.tsx create mode 100644 app/client/src/constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig.ts create mode 100644 app/client/src/pages/Editor/DataSourceEditor/JSONtoForm.tsx create mode 100644 app/client/src/pages/Editor/Explorer/helpers.test.ts create mode 100644 app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx create mode 100644 app/client/src/pages/Editor/SaaSEditor/ActionForm.tsx create mode 100644 app/client/src/pages/Editor/SaaSEditor/DatasourceCard.tsx create mode 100644 app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx create mode 100644 app/client/src/pages/Editor/SaaSEditor/ListView.tsx create mode 100644 app/client/src/pages/Editor/SaaSEditor/constants.ts create mode 100644 app/client/src/pages/Editor/SaaSEditor/routes.ts create mode 100644 app/client/src/pages/common/NotFound.tsx create mode 100644 app/client/src/sagas/SaaSPaneSagas.ts create mode 100644 app/client/src/sagas/UtilSagas.ts create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/plugin.properties create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/pom.xml create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/AppendMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/BulkAppendMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/BulkUpdateMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/ClearMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/CopyMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/CreateMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/DeleteRowMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/DeleteSheetMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/GetValuesMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/GoogleSheetsMethodStrategy.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/InfoMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/ListSheetsMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/Method.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/MethodConfig.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/MethodIdentifiers.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/config/UpdateMethod.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/constants/GoogleSheets.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/domains/RowObject.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/plugins/GoogleSheetsPlugin.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/utils/JSONUtils.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/java/com/external/utils/SheetsUtil.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/resources/editor.json create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/main/resources/form.json create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/config/GetValuesMethodTest.java create mode 100644 app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/config/ListSheetsMethodTest.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Appsmith.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/SaasController.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/IntegrationDTO.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/services/AuthenticationValidator.java diff --git a/.env.example b/.env.example index 91c1b675db..ef48c3bc9d 100644 --- a/.env.example +++ b/.env.example @@ -56,3 +56,5 @@ APPSMITH_MAIL_SMTP_TLS_ENABLED= #APPSMITH_SENTRY_DSN= #APPSMITH_SENTRY_ENVIRONMENT= +# Configure cloud services +# APPSMITH_CLOUD_SERVICES_BASE_URL \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/BindApi_withPageload_Input_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/BindApi_withPageload_Input_spec.js index f6057bc596..163331e028 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/BindApi_withPageload_Input_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/BindApi_withPageload_Input_spec.js @@ -1,9 +1,7 @@ const testdata = require("../../../../fixtures/testdata.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json"); const commonlocators = require("../../../../locators/commonlocators.json"); -const formWidgetsPage = require("../../../../locators/FormWidgets.json"); const dsl = require("../../../../fixtures/MultipleInput.json"); -const pages = require("../../../../locators/Pages.json"); const widgetsPage = require("../../../../locators/Widgets.json"); const publish = require("../../../../locators/publishWidgetspage.json"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Modal_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Modal_spec.js index 9bb36c8c10..ba257c5ae2 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Modal_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Modal_spec.js @@ -1,5 +1,4 @@ const dsl = require("../../../../fixtures/ModalDsl.json"); -const publishPage = require("../../../../locators/publishWidgetspage.json"); const explorer = require("../../../../locators/explorerlocators.json"); describe("Modal Widget Functionality", function() { diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 958ccda954..de5ce8f31c 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1929,7 +1929,7 @@ Cypress.Commands.add("deleteDatasource", (datasourceName) => { }); Cypress.Commands.add("runQuery", () => { - cy.get(queryEditor.runQuery).click(); + cy.get(queryEditor.runQuery).click({ force: true }); cy.wait("@postExecute").should( "have.nested.property", "response.body.responseMeta.status", diff --git a/app/client/docker/templates/nginx-app.conf.template b/app/client/docker/templates/nginx-app.conf.template index 07e0ed2601..4b7c5fdf4e 100644 --- a/app/client/docker/templates/nginx-app.conf.template +++ b/app/client/docker/templates/nginx-app.conf.template @@ -47,6 +47,7 @@ server { sub_filter __APPSMITH_INTERCOM_APP_ID__ '${APPSMITH_INTERCOM_APP_ID}'; sub_filter __APPSMITH_MAIL_ENABLED__ '${APPSMITH_MAIL_ENABLED}'; sub_filter __APPSMITH_DISABLE_TELEMETRY__ '${APPSMITH_DISABLE_TELEMETRY}'; + sub_filter __APPSMITH_CLOUD_SERVICES_BASE_URL__ '${APPSMITH_CLOUD_SERVICES_BASE_URL}'; } location /f { diff --git a/app/client/package.json b/app/client/package.json index 79bd92c5e4..19a022a10e 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -73,6 +73,7 @@ "lint-staged": "^9.2.5", "localforage": "^1.7.3", "lodash": "^4.17.19", + "lodash-es": "4.17.14", "lodash-move": "^1.1.1", "loglevel": "^1.6.7", "lottie-web": "^5.7.4", @@ -118,7 +119,6 @@ "redux-form": "^8.2.6", "redux-saga": "^1.1.3", "reselect": "^4.0.0", - "lodash-es": "4.17.14", "scroll-into-view-if-needed": "^2.2.26", "shallowequal": "^1.1.0", "showdown": "^1.9.1", @@ -186,6 +186,7 @@ "@types/react-beautiful-dnd": "^11.0.4", "@types/react-select": "^3.0.5", "@types/react-tabs": "^2.3.1", + "@types/react-test-renderer": "^17.0.1", "@types/react-window": "^1.8.2", "@types/redux-form": "^8.1.9", "@types/redux-mock-store": "^1.0.2", diff --git a/app/client/public/index.html b/app/client/public/index.html index d4e841f00b..7c54e01533 100755 --- a/app/client/public/index.html +++ b/app/client/public/index.html @@ -159,6 +159,7 @@ intercomAppID: APP_ID, mailEnabled: parseConfig("__APPSMITH_MAIL_ENABLED__"), disableTelemetry: DISABLE_TELEMETRY === "" || DISABLE_TELEMETRY, + cloudServicesBaseUrl: parseConfig("__APPSMITH_CLOUD_SERVICES_BASE_URL__") }; diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts index cffe0e5181..6fa834fd62 100644 --- a/app/client/src/actions/datasourceActions.ts +++ b/app/client/src/actions/datasourceActions.ts @@ -5,6 +5,7 @@ import { } from "constants/ReduxActionConstants"; import { CreateDatasourceConfig } from "api/DatasourcesApi"; import { Datasource } from "entities/Datasource"; +import { PluginType } from "entities/Action"; export const createDatasourceFromForm = (payload: CreateDatasourceConfig) => { return { @@ -44,12 +45,14 @@ export const updateDatasourceSuccess = ( export const redirectAuthorizationCode = ( pageId: string, datasourceId: string, + pluginType: PluginType, ) => { return { type: ReduxActionTypes.REDIRECT_AUTHORIZATION_CODE, payload: { pageId, datasourceId, + pluginType, }, }; }; @@ -105,10 +108,16 @@ export const testDatasource = (payload: Partial) => { }; }; -export const deleteDatasource = (payload: Partial) => { +export const deleteDatasource = ( + payload: Partial, + onSuccess?: ReduxAction, + onError?: ReduxAction, +): ReduxActionWithCallbacks, unknown, unknown> => { return { type: ReduxActionTypes.DELETE_DATASOURCE_INIT, payload, + onSuccess, + onError, }; }; @@ -128,15 +137,6 @@ export const fetchDatasources = () => { }; }; -export const selectPlugin = (pluginId: string) => { - return { - type: ReduxActionTypes.SELECT_PLUGIN, - payload: { - pluginId, - }, - }; -}; - export const initDatasourcePane = ( pluginType: string, urlId?: string, @@ -153,8 +153,14 @@ export const storeAsDatasource = () => { }; }; +export const getOAuthAccessToken = (datasourceId: string) => { + return { + type: ReduxActionTypes.SAAS_GET_OAUTH_ACCESS_TOKEN, + payload: { datasourceId }, + }; +}; + export default { fetchDatasources, initDatasourcePane, - selectPlugin, }; diff --git a/app/client/src/actions/utilActions.ts b/app/client/src/actions/utilActions.ts new file mode 100644 index 0000000000..9597b4b119 --- /dev/null +++ b/app/client/src/actions/utilActions.ts @@ -0,0 +1,15 @@ +import { ReduxActionTypes } from "constants/ReduxActionConstants"; + +export const historyPush = (url: string) => ({ + type: ReduxActionTypes.HISTORY_PUSH, + payload: { + url, + }, +}); + +export const windowRedirect = (url: string) => ({ + type: ReduxActionTypes.REDIRECT_WINDOW_LOCATION, + payload: { + url, + }, +}); diff --git a/app/client/src/api/CloudServicesApi.ts b/app/client/src/api/CloudServicesApi.ts new file mode 100644 index 0000000000..c4abc26056 --- /dev/null +++ b/app/client/src/api/CloudServicesApi.ts @@ -0,0 +1,6 @@ +import { getAppsmithConfigs } from "configs"; + +const { cloudServicesBaseUrl: BASE_URL } = getAppsmithConfigs(); + +export const authorizeSaasWithAppsmithToken = (appsmithToken: string) => + `${BASE_URL}/api/v1/integrations/oauth/authorize?appsmithToken=${appsmithToken}`; diff --git a/app/client/src/api/PluginApi.ts b/app/client/src/api/PluginApi.ts index 6c46bf1a7d..2a14da0364 100644 --- a/app/client/src/api/PluginApi.ts +++ b/app/client/src/api/PluginApi.ts @@ -1,11 +1,12 @@ import Api from "api/Api"; import { AxiosPromise } from "axios"; import { GenericApiResponse } from "api/ApiResponses"; +import { PluginType } from "entities/Action"; export interface Plugin { id: string; name: string; - type: "API" | "DB"; + type: PluginType; packageName: string; iconLocation?: string; uiComponent: "ApiEditorForm" | "RapidApiEditorForm" | "DbEditorForm"; diff --git a/app/client/src/api/SaasApi.ts b/app/client/src/api/SaasApi.ts new file mode 100644 index 0000000000..b463883d62 --- /dev/null +++ b/app/client/src/api/SaasApi.ts @@ -0,0 +1,25 @@ +import Api from "./Api"; +import { AxiosPromise } from "axios"; +import { GenericApiResponse } from "api/ApiResponses"; +import { Datasource } from "entities/Datasource"; + +class SaasApi extends Api { + static url = "v1/saas"; + static getAppsmithToken( + datasourceId: string, + pageId: string, + ): AxiosPromise> { + return Api.post(`${SaasApi.url}/${datasourceId}/pages/${pageId}/oauth`); + } + + static getAccessToken( + datasourceId: string, + token: string, + ): AxiosPromise> { + return Api.post( + `${SaasApi.url}/${datasourceId}/token?appsmithToken=${token}`, + ); + } +} + +export default SaasApi; diff --git a/app/client/src/components/editorComponents/ActionCreator.tsx b/app/client/src/components/editorComponents/ActionCreator.tsx index ec8c70e790..3996db1967 100644 --- a/app/client/src/components/editorComponents/ActionCreator.tsx +++ b/app/client/src/components/editorComponents/ActionCreator.tsx @@ -29,6 +29,7 @@ import { NavigationTargetType } from "sagas/ActionExecutionSagas"; import { checkCurrentStep } from "sagas/OnboardingSagas"; import { OnboardingStep } from "constants/OnboardingConstants"; import { getWidgets } from "sagas/selectors"; +import { PluginType } from "entities/Action"; import { Skin } from "constants/DefaultTheme"; /* eslint-disable @typescript-eslint/ban-types */ @@ -1102,7 +1103,9 @@ function useApiOptionTree() { const pageId = useSelector(getCurrentPageId) || ""; const actions = useSelector(getActionsForCurrentPage).filter( - (action) => action.config.pluginType === "API", + (action) => + action.config.pluginType === PluginType.API || + action.config.pluginType === PluginType.SAAS, ); let filteredBaseOptions = baseOptions; @@ -1173,7 +1176,7 @@ function useQueryOptionTree() { const pageId = useSelector(getCurrentPageId) || ""; const queries = useSelector(getActionsForCurrentPage).filter( - (action) => action.config.pluginType === "DB", + (action) => action.config.pluginType === PluginType.DB, ); const queryOptionTree = getQueryOptionsWithChildren(baseOptions, queries, { label: "Create Query", diff --git a/app/client/src/components/editorComponents/LightningMenu/hooks.ts b/app/client/src/components/editorComponents/LightningMenu/hooks.ts index 66af064609..96eaf0aec2 100644 --- a/app/client/src/components/editorComponents/LightningMenu/hooks.ts +++ b/app/client/src/components/editorComponents/LightningMenu/hooks.ts @@ -1,7 +1,7 @@ import { useSelector } from "react-redux"; import { AppState } from "reducers"; import { WidgetProps } from "widgets/BaseWidget"; -import { Action } from "entities/Action"; +import { Action, PluginType } from "entities/Action"; export const useWidgets = () => { return useSelector((state: AppState) => { @@ -22,14 +22,18 @@ export const useActions = () => { ); }); const apis: Action[] = actions - .filter((action) => action.config.pluginType === "API") + .filter((action) => action.config.pluginType === PluginType.API) .map((action) => action.config); const queries: Action[] = actions - .filter((action) => action.config.pluginType === "DB") + .filter((action) => action.config.pluginType === PluginType.DB) .map((action) => action.config); - return { apis, queries }; + const saas: Action[] = actions + .filter((action) => action.config.pluginType === PluginType.SAAS) + .map((action) => action.config); + + return { apis, queries, saas }; }; export const usePageId = () => { diff --git a/app/client/src/components/editorComponents/LightningMenu/index.tsx b/app/client/src/components/editorComponents/LightningMenu/index.tsx index aa7ffe5245..016b6c1789 100644 --- a/app/client/src/components/editorComponents/LightningMenu/index.tsx +++ b/app/client/src/components/editorComponents/LightningMenu/index.tsx @@ -70,7 +70,7 @@ type LightningMenuProps = { export const LightningMenu = (props: LightningMenuProps) => { const widgets = useWidgets(); - const { apis, queries } = useActions(); + const { apis, queries, saas } = useActions(); const pageId = usePageId(); const dispatch = useDispatch(); @@ -78,7 +78,7 @@ export const LightningMenu = (props: LightningMenuProps) => {
{props.children}
; + +const ReduxFormDecorator = reduxForm({ + form: "TestForm", + initialValues: { actionConfiguration: { testPath: "My test value" } }, +})(TestForm); + +describe("DynamicInputTextControl", () => { + beforeEach(() => { + mockCodemirrorRender(); + }); + it("renders correctly", () => { + render( + + + , + {}, + ); + + const input = screen.getAllByText("My test value")[0]; + userEvent.type(input, "New text"); + waitFor(async () => { + await expect(screen.getAllByText("New text")).toHaveLength(2); + await expect(screen.findByText("My test value")).toBeNull(); + }); + }); +}); diff --git a/app/client/src/components/formControls/DynamicInputTextControl.tsx b/app/client/src/components/formControls/DynamicInputTextControl.tsx index 48a34beb85..6d28893378 100644 --- a/app/client/src/components/formControls/DynamicInputTextControl.tsx +++ b/app/client/src/components/formControls/DynamicInputTextControl.tsx @@ -7,6 +7,7 @@ import { AppState } from "reducers"; import { formValueSelector } from "redux-form"; import { QUERY_EDITOR_FORM_NAME } from "constants/forms"; import { connect } from "react-redux"; +import { actionPathFromName } from "components/formControls/utils"; export function InputText(props: { label: string; @@ -57,17 +58,10 @@ export interface DynamicInputControlProps extends ControlProps { actionName: string; } -const actionPathFromName = (actionName: string, name: string): string => { - const ActionConfigStarts = "actionConfiguration."; - let path = name; - if (path.startsWith(ActionConfigStarts)) { - path = "config." + path.substr(ActionConfigStarts.length); - } - return `${actionName}.${path}`; -}; - -const valueSelector = formValueSelector(QUERY_EDITOR_FORM_NAME); -const mapStateToProps = (state: AppState) => { +const mapStateToProps = (state: AppState, props: DynamicInputControlProps) => { + const valueSelector = formValueSelector( + props.formName || QUERY_EDITOR_FORM_NAME, + ); const actionName = valueSelector(state, "name"); return { actionName: actionName, diff --git a/app/client/src/components/formControls/DynamicTextFieldControl.test.tsx b/app/client/src/components/formControls/DynamicTextFieldControl.test.tsx new file mode 100644 index 0000000000..6e69972f5a --- /dev/null +++ b/app/client/src/components/formControls/DynamicTextFieldControl.test.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import { render, screen } from "test/testUtils"; +import DynamicTextFieldControl from "./DynamicTextFieldControl"; +import { reduxForm } from "redux-form"; +import { mockCodemirrorRender } from "test/__mocks__/CodeMirrorEditorMock"; +import { PluginType } from "entities/Action"; +import { waitFor } from "@testing-library/dom"; +import userEvent from "@testing-library/user-event"; + +const TestForm = (props: any) =>
{props.children}
; + +const ReduxFormDecorator = reduxForm({ + form: "TestForm", + initialValues: { name: "TestAction", datasource: { pluginId: "123" } }, +})(TestForm); + +describe("DynamicTextFieldControl", () => { + beforeEach(() => { + mockCodemirrorRender(); + }); + it("renders template menu correctly", () => { + render( + + + , + { + url: "/?showTemplate=true", + initialState: { + entities: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + plugins: { + list: [ + { + id: "123", + name: "testPlugin", + type: PluginType.DB, + packageName: "DB", + templates: { + CREATE: "test plugin template", + }, + uiComponent: "DbEditorForm", + datasourceComponent: "AutoForm", + }, + ], + }, + }, + }, + }, + ); + const createTemplateButton = screen.getByText("Create"); + userEvent.click(createTemplateButton); + expect(screen.getByText("test plugin template")).toBeDefined(); + waitFor(async () => { + await expect(screen.findByText("Create")).toBeNull(); + }); + }); +}); diff --git a/app/client/src/components/formControls/DynamicTextFieldControl.tsx b/app/client/src/components/formControls/DynamicTextFieldControl.tsx index a52d098597..bc764a9efe 100644 --- a/app/client/src/components/formControls/DynamicTextFieldControl.tsx +++ b/app/client/src/components/formControls/DynamicTextFieldControl.tsx @@ -21,6 +21,7 @@ import { convertObjectToQueryParams, getQueryParams, } from "utils/AppsmithUtils"; +import { actionPathFromName } from "components/formControls/utils"; const Wrapper = styled.div` .dynamic-text-field { @@ -57,7 +58,14 @@ class DynamicTextControl extends BaseControl< } render() { - const { responseType, label } = this.props; + const { + responseType, + label, + placeholderText, + actionName, + configProperty, + } = this.props; + const dataTreePath = actionPathFromName(actionName, configProperty); const isNewQuery = new URLSearchParams(window.location.search).get("showTemplate") === "true"; @@ -78,7 +86,11 @@ class DynamicTextControl extends BaseControl< { showTemplateMenu: false, }, - () => this.props.createTemplate(templateString), + () => + this.props.createTemplate( + templateString, + this.props.formName, + ), ); }} pluginId={this.props.pluginId} @@ -87,10 +99,11 @@ class DynamicTextControl extends BaseControl< )} @@ -100,13 +113,16 @@ class DynamicTextControl extends BaseControl< export interface DynamicTextFieldProps extends ControlProps { actionName: string; - createTemplate: (template: any) => any; + createTemplate: (template: any, formName: string) => any; pluginId: string; responseType: string; + placeholderText?: string; } -const valueSelector = formValueSelector(QUERY_EDITOR_FORM_NAME); -const mapStateToProps = (state: AppState) => { +const mapStateToProps = (state: AppState, props: DynamicTextFieldProps) => { + const valueSelector = formValueSelector( + props.formName || QUERY_EDITOR_FORM_NAME, + ); const actionName = valueSelector(state, "name"); const pluginId = valueSelector(state, "datasource.pluginId"); const responseTypes = getPluginResponseTypes(state); @@ -119,7 +135,7 @@ const mapStateToProps = (state: AppState) => { }; const mapDispatchToProps = (dispatch: any) => ({ - createTemplate: (template: any) => { + createTemplate: (template: any, formName: string) => { const params = getQueryParams(); if (params.showTemplate) { params.showTemplate = "false"; @@ -128,7 +144,9 @@ const mapDispatchToProps = (dispatch: any) => ({ ...window.location, search: convertObjectToQueryParams(params), }); - dispatch(change(QUERY_EDITOR_FORM_NAME, QUERY_BODY_FIELD, template)); + dispatch( + change(formName || QUERY_EDITOR_FORM_NAME, QUERY_BODY_FIELD, template), + ); }, }); diff --git a/app/client/src/components/formControls/utils.test.ts b/app/client/src/components/formControls/utils.test.ts index 19b3db3230..f80fcd3679 100644 --- a/app/client/src/components/formControls/utils.test.ts +++ b/app/client/src/components/formControls/utils.test.ts @@ -3,6 +3,7 @@ import { getConfigInitialValues, caculateIsHidden, evaluateCondtionWithType, + actionPathFromName, } from "./utils"; import { HiddenType } from "./BaseControl"; @@ -304,3 +305,13 @@ describe("evaluateCondtionWithType test", () => { ).toBeTruthy(); }); }); + +describe("actionPathFromName test", () => { + it("creates path from name", () => { + const actionName = "Api5"; + const name = "actionConfiguration.pluginSpecifiedTemplates[7].value"; + const pathName = "Api5.config.pluginSpecifiedTemplates[7].value"; + + expect(actionPathFromName(actionName, name)).toEqual(pathName); + }); +}); diff --git a/app/client/src/components/formControls/utils.ts b/app/client/src/components/formControls/utils.ts index de74de4fcf..9f46143ffa 100644 --- a/app/client/src/components/formControls/utils.ts +++ b/app/client/src/components/formControls/utils.ts @@ -133,3 +133,15 @@ export const getConfigInitialValues = (config: Record[]) => { return configInitialValues; }; + +export const actionPathFromName = ( + actionName: string, + name: string, +): string => { + const ActionConfigStarts = "actionConfiguration."; + let path = name; + if (path.startsWith(ActionConfigStarts)) { + path = "config." + path.substr(ActionConfigStarts.length); + } + return `${actionName}.${path}`; +}; diff --git a/app/client/src/configs/index.ts b/app/client/src/configs/index.ts index 50d6923304..1884c540c8 100644 --- a/app/client/src/configs/index.ts +++ b/app/client/src/configs/index.ts @@ -41,6 +41,7 @@ export type INJECTED_CONFIGS = { intercomAppID: string; mailEnabled: boolean; disableTelemetry: boolean; + cloudServicesBaseUrl: string; }; declare global { interface Window { @@ -113,6 +114,7 @@ const getConfigsFromEnvVars = (): INJECTED_CONFIGS => { ? process.env.REACT_APP_MAIL_ENABLED.length > 0 : false, disableTelemetry: true, + cloudServicesBaseUrl: process.env.REACT_APP_CLOUD_SERVICES_BASE_URL || "", }; }; @@ -263,5 +265,8 @@ export const getAppsmithConfigs = (): AppsmithUIConfigs => { ENV_CONFIG.intercomAppID || APPSMITH_FEATURE_CONFIGS.intercomAppID, mailEnabled: ENV_CONFIG.mailEnabled || APPSMITH_FEATURE_CONFIGS.mailEnabled, disableTelemetry: APPSMITH_FEATURE_CONFIGS.disableTelemetry, + cloudServicesBaseUrl: + ENV_CONFIG.cloudServicesBaseUrl || + APPSMITH_FEATURE_CONFIGS.cloudServicesBaseUrl, }; }; diff --git a/app/client/src/configs/types.ts b/app/client/src/configs/types.ts index a401061cd6..a5f4bde58f 100644 --- a/app/client/src/configs/types.ts +++ b/app/client/src/configs/types.ts @@ -69,4 +69,6 @@ export type AppsmithUIConfigs = { mailEnabled: boolean; disableTelemetry: boolean; + + cloudServicesBaseUrl: string; }; diff --git a/app/client/src/constants/ApiEditorConstants.ts b/app/client/src/constants/ApiEditorConstants.ts index 3b5764500a..85efc7b576 100644 --- a/app/client/src/constants/ApiEditorConstants.ts +++ b/app/client/src/constants/ApiEditorConstants.ts @@ -35,7 +35,6 @@ export const DEFAULT_API_ACTION_CONFIG: ApiActionConfig = { queryParameters: EMPTY_KEY_VALUE_PAIRS.slice(), }; -export const PLUGIN_TYPE_API = "API"; export const DEFAULT_PROVIDER_OPTION = "Business Software"; export const CONTENT_TYPE_HEADER_KEY = "content-type"; diff --git a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx index 38969d6542..d36d9d1e62 100644 --- a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx +++ b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx @@ -4,6 +4,7 @@ import { PluginType } from "entities/Action"; import queryActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/QuerySettingsConfig"; import apiActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/ApiSettingsConfig"; import apiActionEditorConfig from "constants/AppsmithActionConstants/formConfig/ApiEditorConfigs"; +import saasActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig"; export type ExecuteActionPayloadEvent = { type: EventType; @@ -109,9 +110,11 @@ export const Swagger = "Swagger"; export const defaultActionSettings: Record = { [PluginType.API]: apiActionSettingsConfig, [PluginType.DB]: queryActionSettingsConfig, + [PluginType.SAAS]: saasActionSettingsConfig, }; export const defaultActionEditorConfigs: Record = { [PluginType.API]: apiActionEditorConfig, [PluginType.DB]: [], + [PluginType.SAAS]: [], }; diff --git a/app/client/src/constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig.ts b/app/client/src/constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig.ts new file mode 100644 index 0000000000..eb875985ff --- /dev/null +++ b/app/client/src/constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig.ts @@ -0,0 +1,27 @@ +export default [ + { + sectionName: "", + id: 1, + children: [ + { + label: "Run API on Page load", + configProperty: "executeOnLoad", + controlType: "SWITCH", + info: "Will refresh data each time the page is loaded", + }, + { + label: "Request confirmation before running API", + configProperty: "confirmBeforeExecute", + controlType: "SWITCH", + info: "Ask confirmation from the user each time before refreshing data", + }, + { + label: "API timeout (in milliseconds)", + info: "Maximum time after which the API will return", + configProperty: "actionConfiguration.timeoutInMillisecond", + controlType: "INPUT_TEXT", + dataType: "NUMBER", + }, + ], + }, +]; diff --git a/app/client/src/constants/QueryEditorConstants.ts b/app/client/src/constants/QueryEditorConstants.ts index f3fc1debad..5bfd35913e 100644 --- a/app/client/src/constants/QueryEditorConstants.ts +++ b/app/client/src/constants/QueryEditorConstants.ts @@ -8,5 +8,3 @@ export const PLUGIN_PACKAGE_DBS = [ PLUGIN_PACKAGE_POSTGRES, PLUGIN_PACKAGE_MONGO, ]; - -export const QUERY_CONSTANT = "DB"; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index f5553caba6..560b754bab 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -116,7 +116,6 @@ export const ReduxActionTypes: { [key: string]: string } = { REFRESH_DATASOURCE_STRUCTURE_INIT: "REFRESH_DATASOURCE_STRUCTURE_INIT", REFRESH_DATASOURCE_STRUCTURE_SUCCESS: "REFRESH_DATASOURCE_STRUCTURE_SUCCESS", EXPAND_DATASOURCE_ENTITY: "EXPAND_DATASOURCE_ENTITY", - SELECT_PLUGIN: "SELECT_PLUGIN", TEST_DATASOURCE_INIT: "TEST_DATASOURCE_INIT", TEST_DATASOURCE_SUCCESS: "TEST_DATASOURCE_SUCCESS", DELETE_DATASOURCE_DRAFT: "DELETE_DATASOURCE_DRAFT", @@ -134,6 +133,8 @@ export const ReduxActionTypes: { [key: string]: string } = { FETCH_PUBLISHED_PAGE_INIT: "FETCH_PUBLISHED_PAGE_INIT", FETCH_PUBLISHED_PAGE_SUCCESS: "FETCH_PUBLISHED_PAGE_SUCCESS", REDIRECT_AUTHORIZATION_CODE: "REDIRECT_AUTHORIZATION_CODE", + REDIRECT_WINDOW_LOCATION: "REDIRECT_WINDOW_LOCATION", + HISTORY_PUSH: "HISTORY_PUSH", DELETE_DATASOURCE_INIT: "DELETE_DATASOURCE_INIT", DELETE_DATASOURCE_SUCCESS: "DELETE_DATASOURCE_SUCCESS", STORE_AS_DATASOURCE_INIT: "STORE_AS_DATASOURCE_INIT", @@ -350,6 +351,7 @@ export const ReduxActionTypes: { [key: string]: string } = { RESET_UNREAD_RELEASES_COUNT: "RESET_UNREAD_RELEASES_COUNT", SET_LOADING_ENTITIES: "SET_LOADING_ENTITIES", RESET_CURRENT_APPLICATION: "RESET_CURRENT_APPLICATION", + SAAS_GET_OAUTH_ACCESS_TOKEN: "SAAS_GET_OAUTH_ACCESS_TOKEN", UPDATE_RECENT_ENTITY: "UPDATE_RECENT_ENTITY", RESTORE_RECENT_ENTITIES_REQUEST: "RESTORE_RECENT_ENTITIES_REQUEST", RESTORE_RECENT_ENTITIES_SUCCESS: "RESTORE_RECENT_ENTITIES_SUCCESS", diff --git a/app/client/src/constants/forms.ts b/app/client/src/constants/forms.ts index a07ba00531..40969148e3 100644 --- a/app/client/src/constants/forms.ts +++ b/app/client/src/constants/forms.ts @@ -17,6 +17,8 @@ export const CURL_IMPORT_FORM = "CurlImportForm"; export const QUERY_EDITOR_FORM_NAME = "QueryEditorForm"; export const API_HOME_SCREEN_FORM = "APIHomeScreenForm"; +export const SAAS_EDITOR_FORM = "SaaSEditorForm"; export const DATASOURCE_DB_FORM = "DatasourceDBForm"; export const DATASOURCE_REST_API_FORM = "DatasourceRestAPIForm"; +export const DATASOURCE_SAAS_FORM = "DatasourceSaaSForm"; diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index 72b168888e..37fb04ac0d 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -263,6 +263,13 @@ export const REST_API_AUTHORIZATION_FAILED = () => export const REST_API_AUTHORIZATION_APPSMITH_ERROR = () => "Something went wrong."; +export const SAAS_AUTHORIZATION_SUCCESSFUL = "Authorization was successful!"; +export const SAAS_AUTHORIZATION_FAILED = + "Authorization failed. Please check your details or try again."; +// Todo: improve this for appsmith_error error message +export const SAAS_AUTHORIZATION_APPSMITH_ERROR = "Something went wrong."; +export const SAAS_APPSMITH_TOKEN_NOT_FOUND = "Appsmith token not found"; + export const LOCAL_STORAGE_QUOTA_EXCEEDED_MESSAGE = () => "Error saving a key in localStorage. You have exceeded the allowed storage size limit"; export const LOCAL_STORAGE_NO_SPACE_LEFT_ON_DEVICE_MESSAGE = () => diff --git a/app/client/src/entities/Action/index.ts b/app/client/src/entities/Action/index.ts index f313ae77ab..090db222b1 100644 --- a/app/client/src/entities/Action/index.ts +++ b/app/client/src/entities/Action/index.ts @@ -5,6 +5,7 @@ import _ from "lodash"; export enum PluginType { API = "API", DB = "DB", + SAAS = "SAAS", } export enum PaginationType { @@ -84,6 +85,11 @@ interface BaseApiAction extends BaseAction { pluginType: PluginType.API; actionConfiguration: ApiActionConfig; } +export interface SaaSAction extends BaseAction { + pluginType: PluginType.SAAS; + actionConfiguration: any; + datasource: StoredDatasource; +} export interface EmbeddedApiAction extends BaseApiAction { datasource: EmbeddedRestDatasource; @@ -118,4 +124,4 @@ export type ActionViewMode = { timeoutInMillisecond?: number; }; -export type Action = ApiAction | QueryAction; +export type Action = ApiAction | QueryAction | SaaSAction; diff --git a/app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx b/app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx index 078fe21647..03cb26bd3d 100644 --- a/app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx +++ b/app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx @@ -10,6 +10,7 @@ import { getCurlImportPageURL, getProviderTemplatesURL, } from "constants/routes"; +import { SAAS_EDITOR_URL } from "pages/Editor/SaaSEditor/constants"; import { AppState } from "reducers"; import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import { getImportedCollections } from "selectors/applicationSelectors"; @@ -47,6 +48,7 @@ import AnalyticsUtil, { EventLocation } from "utils/AnalyticsUtil"; import { getAppsmithConfigs } from "configs"; import { getAppCardColorPalette } from "selectors/themeSelectors"; import { CURL } from "constants/AppsmithActionConstants/ActionConstants"; +import { PluginType } from "entities/Action"; const { enableRapidAPI } = getAppsmithConfigs(); const SearchContainer = styled.div` @@ -182,6 +184,9 @@ const StyledContainer = styled.div` .curlImage { width: 55px; } + .saasImage.t--saas-google-sheets-plugin-image { + width: 40px; + } .createIcon { align-items: center; margin-top: 15px; @@ -591,6 +596,31 @@ class ApiHomeScreen extends React.Component {

CURL

+ {/** + * Loop over all Saas plugins + */} + {this.props.plugins + .filter((p) => p.type === PluginType.SAAS) + .map((p) => ( + + + {p.name} +

{p.name}

+
+ + ))} {/* Imported APIs section start */} diff --git a/app/client/src/pages/Editor/APIEditor/index.tsx b/app/client/src/pages/Editor/APIEditor/index.tsx index 4fce4f24de..cf5849aa8e 100644 --- a/app/client/src/pages/Editor/APIEditor/index.tsx +++ b/app/client/src/pages/Editor/APIEditor/index.tsx @@ -34,9 +34,8 @@ import PerformanceTracker, { import * as Sentry from "@sentry/react"; import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane"; import { ApplicationPayload } from "constants/ReduxActionConstants"; -import { getThemeDetails, ThemeMode } from "selectors/themeSelectors"; -import { Theme } from "constants/DefaultTheme"; import { getPluginSettingConfigs } from "selectors/entitiesSelector"; +import { SAAS_EDITOR_API_ID_URL } from "../SaaSEditor/constants"; const LoadingContainer = styled(CenteredWrapper)` height: 50%; @@ -57,7 +56,6 @@ interface ReduxStateProps { apiAction: Action | ActionData | RapidApiAction | undefined; paginationType: PaginationType; isEditorInitialized: boolean; - lightTheme: Theme; } interface ReduxActionProps { submitForm: (name: string) => void; @@ -223,6 +221,16 @@ class ApiEditor extends React.Component { location={this.props.location} /> )} + + {formUiComponent === "SaaSEditorForm" && + this.props.history.push( + SAAS_EDITOR_API_ID_URL( + this.props.match.params.applicationId, + this.props.match.params.pageId, + this.props.plugins[this.props.pluginId]?.packageName ?? "", + this.props.match.params.apiId, + ), + )} ) : ( apiHomeScreen @@ -253,7 +261,6 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => { isDeleting: isDeleting[props.match.params.apiId], isCreating: isCreating, isEditorInitialized: getIsEditorInitialized(state), - lightTheme: getThemeDetails(state, ThemeMode.LIGHT), }; }; diff --git a/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx index e2f201caa3..6c38af8130 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx @@ -3,12 +3,10 @@ import styled from "styled-components"; import _ from "lodash"; import { DATASOURCE_DB_FORM } from "constants/forms"; import { DATA_SOURCES_EDITOR_URL } from "constants/routes"; -import FormControl from "../FormControl"; -import Collapsible from "./Collapsible"; + import history from "utils/history"; import { Icon } from "@blueprintjs/core"; import FormTitle from "./FormTitle"; -import { ControlProps } from "components/formControls/BaseControl"; import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleHelp"; import Connected from "./Connected"; @@ -17,7 +15,6 @@ import { HelpBaseURL, HelpMap } from "constants/HelpConstants"; import Button from "components/editorComponents/Button"; import { Datasource } from "entities/Datasource"; import { reduxForm, InjectedFormProps } from "redux-form"; -import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import { APPSMITH_IP_ADDRESSES } from "constants/DatasourceEditorConstants"; import { getAppsmithConfigs } from "configs"; import AnalyticsUtil from "utils/AnalyticsUtil"; @@ -26,93 +23,38 @@ import BackButton from "./BackButton"; import { PluginType } from "entities/Action"; import Boxed from "components/editorComponents/Onboarding/Boxed"; import { OnboardingStep } from "constants/OnboardingConstants"; -import { isHidden } from "components/formControls/utils"; -import log from "loglevel"; -import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; +import { + ActionButton, + FormTitleContainer, + Header, + JSONtoForm, + JSONtoFormProps, + PluginImage, + SaveButtonContainer, +} from "./JSONtoForm"; const { cloudHosting } = getAppsmithConfigs(); -interface DatasourceDBEditorProps { +interface DatasourceDBEditorProps extends JSONtoFormProps { onSave: (formValues: Datasource) => void; onTest: (formValus: Datasource) => void; handleDelete: (id: string) => void; setDatasourceEditorMode: (id: string, viewMode: boolean) => void; - selectedPluginPackage: string; isSaving: boolean; isDeleting: boolean; datasourceId: string; applicationId: string; pageId: string; - formData: Datasource; isTesting: boolean; - formConfig: any[]; isNewDatasource: boolean; pluginImage: string; viewMode: boolean; pluginType: string; } -interface DatasourceDBEditorState { - viewMode: boolean; -} - type Props = DatasourceDBEditorProps & InjectedFormProps; -export const LoadingContainer = styled(CenteredWrapper)` - height: 50%; -`; - -const DBForm = styled.div` - padding: 20px; - margin-left: 10px; - margin-right: 0px; - height: calc(100vh - ${(props) => props.theme.smallHeaderHeight}); - overflow: auto; - .backBtn { - padding-bottom: 1px; - cursor: pointer; - } - .backBtnText { - font-size: 16px; - font-weight: 500; - cursor: pointer; - } -`; - -const PluginImage = styled.img` - height: 40px; - width: auto; -`; - -export const FormTitleContainer = styled.div` - flex-direction: row; - display: flex; - align-items: center; -`; - -export const Header = styled.div` - flex-direction: row; - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 16px; -`; - -const SaveButtonContainer = styled.div` - margin-top: 24px; - display: flex; - justify-content: flex-end; -`; - -const ActionButton = styled(BaseButton)` - &&& { - max-width: 72px; - margin-right: 9px; - min-height: 32px; - } -`; - const StyledButton = styled(Button)` &&&& { width: 87px; @@ -131,158 +73,14 @@ const CollapsibleWrapper = styled.div` width: max-content; `; -class DatasourceDBEditor extends React.Component< - Props, - DatasourceDBEditorState -> { - requiredFields: Record; - configDetails: Record; - constructor(props: Props) { - super(props); - - this.state = { - viewMode: true, - }; - this.requiredFields = {}; - this.configDetails = {}; - } - - componentDidMount() { - this.requiredFields = {}; - this.configDetails = {}; - } - +class DatasourceDBEditor extends JSONtoForm { componentDidUpdate(prevProps: Props) { if (prevProps.datasourceId !== this.props.datasourceId) { - this.requiredFields = {}; - this.configDetails = {}; + super.componentDidUpdate(prevProps); this.props.setDatasourceEditorMode(this.props.datasourceId, true); } } - validate = () => { - const errors = {} as any; - const requiredFields = Object.keys(this.requiredFields); - const values = this.props.formData; - - requiredFields.forEach((fieldConfigProperty) => { - const fieldConfig = this.requiredFields[fieldConfigProperty]; - if (fieldConfig.controlType === "KEYVALUE_ARRAY") { - const configProperty = fieldConfig.configProperty.split("[*]."); - const arrayValues = _.get(values, configProperty[0]); - const keyValueArrayErrors: Record[] = []; - - arrayValues.forEach((value: any, index: number) => { - const objectKeys = Object.keys(value); - const keyValueErrors: Record = {}; - - if (!value[objectKeys[0]]) { - keyValueErrors[objectKeys[0]] = "This field is required"; - keyValueArrayErrors[index] = keyValueErrors; - } - if (!value[objectKeys[1]]) { - keyValueErrors[objectKeys[1]] = "This field is required"; - keyValueArrayErrors[index] = keyValueErrors; - } - }); - - if (keyValueArrayErrors.length) { - _.set(errors, configProperty[0], keyValueArrayErrors); - } - } else if (fieldConfig.controlType === "KEY_VAL_INPUT") { - const value = _.get(values, fieldConfigProperty, []); - - if (value.length) { - const values = Object.values(value[0]); - const isNotBlank = values.every((value) => value); - - if (!isNotBlank) { - _.set(errors, fieldConfigProperty, "This field is required"); - } - } - } else { - const value = _.get(values, fieldConfigProperty); - - if (!value) { - _.set(errors, fieldConfigProperty, "This field is required"); - } - } - }); - - return !_.isEmpty(errors) || this.props.invalid; - }; - - render() { - const { formConfig } = this.props; - const content = this.renderDataSourceConfigForm(formConfig); - return {content}; - } - - isNewDatasource = () => { - const { datasourceId } = this.props; - - return datasourceId.includes(":"); - }; - - normalizeValues = () => { - let { formData } = this.props; - const checked: Record = {}; - const configProperties = Object.keys(this.configDetails); - - for (const configProperty of configProperties) { - const controlType = this.configDetails[configProperty]; - - if (controlType === "KEYVALUE_ARRAY") { - const properties = configProperty.split("[*]."); - - if (checked[properties[0]]) continue; - - checked[properties[0]] = 1; - const values = _.get(formData, properties[0]); - const newValues: ({ [s: string]: unknown } | ArrayLike)[] = []; - - values.forEach( - (object: { [s: string]: unknown } | ArrayLike) => { - const isEmpty = Object.values(object).every((x) => x === ""); - - if (!isEmpty) { - newValues.push(object); - } - }, - ); - - if (newValues.length) { - formData = _.set(formData, properties[0], newValues); - } else { - formData = _.set(formData, properties[0], []); - } - } else if (controlType === "KEY_VAL_INPUT") { - if (checked[configProperty]) continue; - - const values = _.get(formData, configProperty); - const newValues: ({ [s: string]: unknown } | ArrayLike)[] = []; - - values.forEach( - (object: { [s: string]: unknown } | ArrayLike) => { - const isEmpty = Object.values(object).every((x) => x === ""); - - if (!isEmpty) { - newValues.push(object); - } - }, - ); - - if (newValues.length) { - formData = _.set(formData, configProperty, newValues); - } else { - formData = _.set(formData, configProperty, []); - } - } - } - - return formData; - }; - save = () => { const normalizedValues = this.normalizeValues(); AnalyticsUtil.logEvent("SAVE_DATA_SOURCE_CLICK", { @@ -301,6 +99,12 @@ class DatasourceDBEditor extends React.Component< this.props.onTest(normalizedValues); }; + render() { + const { formConfig } = this.props; + const content = this.renderDataSourceConfigForm(formConfig); + return this.renderForm(content); + } + renderDataSourceConfigForm = (sections: any) => { const { isSaving, @@ -403,83 +207,6 @@ class DatasourceDBEditor extends React.Component< ); }; - - renderMainSection = (section: any, index: number) => { - if (isHidden(this.props.formData, section.hidden)) return null; - return ( - - {this.renderEachConfig(section)} - - ); - }; - - renderSingleConfig = ( - config: ControlProps, - multipleConfig?: ControlProps[], - ) => { - multipleConfig = multipleConfig || []; - try { - this.setupConfig(config); - return ( -
- -
- ); - } catch (e) { - log.error(e); - } - }; - - setupConfig = (config: ControlProps) => { - const { controlType, isRequired, configProperty } = config; - this.configDetails[configProperty] = controlType; - - if (isRequired) { - this.requiredFields[configProperty] = config; - } - }; - - isKVArray = (children: Array) => { - if (!Array.isArray(children) || children.length < 2) return false; - return ( - children[0].controlType && children[0].controlType === "KEYVALUE_ARRAY" - ); - }; - - renderKVArray = (children: Array) => { - try { - // setup config for each child - children.forEach((c) => this.setupConfig(c)); - // We pass last child for legacy reasons, to keep the logic here exactly same as before. - return this.renderSingleConfig(children[children.length - 1], children); - } catch (e) { - log.error(e); - } - }; - - renderEachConfig = (section: any) => { - return ( -
- {_.map(section.children, (propertyControlOrSection: ControlProps) => { - // If the section is hidden, skip rendering - if (isHidden(this.props.formData, section.hidden)) return null; - if ("children" in propertyControlOrSection) { - const { children } = propertyControlOrSection; - if (this.isKVArray(children)) { - return this.renderKVArray(children); - } - return this.renderEachConfig(propertyControlOrSection); - } else { - return this.renderSingleConfig(propertyControlOrSection); - } - })} -
- ); - }; } export default reduxForm({ diff --git a/app/client/src/pages/Editor/DataSourceEditor/DatasourceHome.tsx b/app/client/src/pages/Editor/DataSourceEditor/DatasourceHome.tsx index ecbb1bd8e0..c61e8aa5c8 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/DatasourceHome.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/DatasourceHome.tsx @@ -7,10 +7,7 @@ import { getDBPlugins, getPluginImages } from "selectors/entitiesSelector"; import history from "utils/history"; import { Plugin } from "api/PluginApi"; import { DATASOURCE_DB_FORM } from "constants/forms"; -import { - selectPlugin, - createDatasourceFromForm, -} from "actions/datasourceActions"; +import { createDatasourceFromForm } from "actions/datasourceActions"; import { AppState } from "reducers"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { getCurrentApplication } from "selectors/applicationSelectors"; @@ -102,7 +99,6 @@ interface DatasourceHomeScreenProps { replace: (data: string) => void; push: (data: string) => void; }; - selectPlugin: (pluginType: string) => void; } interface ReduxDispatchProps { @@ -127,7 +123,6 @@ class DatasourceHomeScreen extends React.Component { plugin: pluginName, }); - this.props.selectPlugin(pluginId); this.props.createDatasource({ pluginId, }); @@ -202,7 +197,6 @@ const mapStateToProps = (state: AppState): ReduxStateProps => { const mapDispatchToProps = (dispatch: any) => { return { - selectPlugin: (pluginId: string) => dispatch(selectPlugin(pluginId)), initializeForm: (data: Record) => dispatch(initialize(DATASOURCE_DB_FORM, data)), createDatasource: (data: any) => dispatch(createDatasourceFromForm(data)), diff --git a/app/client/src/pages/Editor/DataSourceEditor/FormTitle.tsx b/app/client/src/pages/Editor/DataSourceEditor/FormTitle.tsx index d7b7665fe5..e437ae67ff 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/FormTitle.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/FormTitle.tsx @@ -6,10 +6,9 @@ import EditableText, { } from "components/editorComponents/EditableText"; import { AppState } from "reducers"; -import { getDatasource } from "selectors/entitiesSelector"; +import { getDatasource, getDatasources } from "selectors/entitiesSelector"; import { useSelector, useDispatch } from "react-redux"; import { Datasource } from "entities/Datasource"; -import { getDataSources } from "selectors/editorSelectors"; import { isNameValid } from "utils/helpers"; import { saveDatasourceName } from "actions/datasourceActions"; import { Spinner } from "@blueprintjs/core"; @@ -37,8 +36,7 @@ const FormTitle = (props: FormTitleProps) => { | undefined = useSelector((state: AppState) => getDatasource(state, params.datasourceId), ); - - const datasources: Datasource[] = useSelector(getDataSources); + const datasources: Datasource[] = useSelector(getDatasources); const [forceUpdate, setForceUpdate] = useState(false); const dispatch = useDispatch(); const saveStatus: { diff --git a/app/client/src/pages/Editor/DataSourceEditor/JSONtoForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/JSONtoForm.tsx new file mode 100644 index 0000000000..5567b588fc --- /dev/null +++ b/app/client/src/pages/Editor/DataSourceEditor/JSONtoForm.tsx @@ -0,0 +1,285 @@ +import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; +import React from "react"; +import styled from "styled-components"; +import _ from "lodash"; +import FormControl from "../FormControl"; +import Collapsible from "./Collapsible"; +import { ControlProps } from "components/formControls/BaseControl"; +import { Datasource } from "entities/Datasource"; +import { isHidden } from "components/formControls/utils"; +import log from "loglevel"; +import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; + +export const LoadingContainer = styled(CenteredWrapper)` + height: 50%; +`; + +export const PluginImage = styled.img` + height: 40px; + width: auto; +`; + +export const FormTitleContainer = styled.div` + flex-direction: row; + display: flex; + align-items: center; +`; + +export const Header = styled.div` + flex-direction: row; + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 16px; +`; + +export const SaveButtonContainer = styled.div` + margin-top: 24px; + display: flex; + justify-content: flex-end; +`; + +export const ActionButton = styled(BaseButton)` + &&& { + max-width: 72px; + margin-right: 9px; + min-height: 32px; + } +`; + +const DBForm = styled.div` + padding: 20px; + margin-left: 10px; + margin-right: 0px; + height: calc(100vh - ${(props) => props.theme.smallHeaderHeight}); + overflow: auto; + .backBtn { + padding-bottom: 1px; + cursor: pointer; + } + .backBtnText { + font-size: 16px; + font-weight: 500; + cursor: pointer; + } +`; + +export interface JSONtoFormProps { + formData: Datasource; + formName: string; + formConfig: any[]; + datasourceId: string; +} + +export class JSONtoForm< + P = unknown, + S = unknown, + SS = any +> extends React.Component { + requiredFields: Record = {}; + configDetails: Record = {}; + + componentDidMount() { + this.requiredFields = {}; + this.configDetails = {}; + } + + componentDidUpdate(prevProps: JSONtoFormProps) { + if (prevProps.datasourceId !== this.props.datasourceId) { + this.requiredFields = {}; + this.configDetails = {}; + } + } + + validate = () => { + const errors = {} as any; + const requiredFields = Object.keys(this.requiredFields); + const values = this.props.formData; + + requiredFields.forEach((fieldConfigProperty) => { + const fieldConfig = this.requiredFields[fieldConfigProperty]; + if (fieldConfig.controlType === "KEYVALUE_ARRAY") { + const configProperty = fieldConfig.configProperty.split("[*]."); + const arrayValues = _.get(values, configProperty[0]); + const keyValueArrayErrors: Record[] = []; + + arrayValues.forEach((value: any, index: number) => { + const objectKeys = Object.keys(value); + const keyValueErrors: Record = {}; + + if (!value[objectKeys[0]]) { + keyValueErrors[objectKeys[0]] = "This field is required"; + keyValueArrayErrors[index] = keyValueErrors; + } + if (!value[objectKeys[1]]) { + keyValueErrors[objectKeys[1]] = "This field is required"; + keyValueArrayErrors[index] = keyValueErrors; + } + }); + + if (keyValueArrayErrors.length) { + _.set(errors, configProperty[0], keyValueArrayErrors); + } + } else if (fieldConfig.controlType === "KEY_VAL_INPUT") { + const value = _.get(values, fieldConfigProperty, []); + + if (value.length) { + const values = Object.values(value[0]); + const isNotBlank = values.every((value) => value); + + if (!isNotBlank) { + _.set(errors, fieldConfigProperty, "This field is required"); + } + } + } else { + const value = _.get(values, fieldConfigProperty); + + if (!value) { + _.set(errors, fieldConfigProperty, "This field is required"); + } + } + }); + + return !_.isEmpty(errors); + }; + + normalizeValues = () => { + let { formData } = this.props; + const checked: Record = {}; + const configProperties = Object.keys(this.configDetails); + + for (const configProperty of configProperties) { + const controlType = this.configDetails[configProperty]; + + if (controlType === "KEYVALUE_ARRAY") { + const properties = configProperty.split("[*]."); + + if (checked[properties[0]]) continue; + + checked[properties[0]] = 1; + const values = _.get(formData, properties[0]); + const newValues: ({ [s: string]: unknown } | ArrayLike)[] = []; + + values.forEach( + (object: { [s: string]: unknown } | ArrayLike) => { + const isEmpty = Object.values(object).every((x) => x === ""); + + if (!isEmpty) { + newValues.push(object); + } + }, + ); + + if (newValues.length) { + formData = _.set(formData, properties[0], newValues); + } else { + formData = _.set(formData, properties[0], []); + } + } else if (controlType === "KEY_VAL_INPUT") { + if (checked[configProperty]) continue; + + const values = _.get(formData, configProperty); + const newValues: ({ [s: string]: unknown } | ArrayLike)[] = []; + + values.forEach( + (object: { [s: string]: unknown } | ArrayLike) => { + const isEmpty = Object.values(object).every((x) => x === ""); + + if (!isEmpty) { + newValues.push(object); + } + }, + ); + + if (newValues.length) { + formData = _.set(formData, configProperty, newValues); + } else { + formData = _.set(formData, configProperty, []); + } + } + } + + return formData; + }; + + renderForm = (content: any) => { + return {content}; + }; + + renderMainSection = (section: any, index: number) => { + if (isHidden(this.props.formData, section.hidden)) return null; + return ( + + {this.renderEachConfig(section)} + + ); + }; + + renderSingleConfig = ( + config: ControlProps, + multipleConfig?: ControlProps[], + ) => { + multipleConfig = multipleConfig || []; + try { + this.setupConfig(config); + return ( +
+ +
+ ); + } catch (e) { + log.error(e); + } + }; + + setupConfig = (config: ControlProps) => { + const { controlType, isRequired, configProperty } = config; + this.configDetails[configProperty] = controlType; + + if (isRequired) { + this.requiredFields[configProperty] = config; + } + }; + + isKVArray = (children: Array) => { + if (!Array.isArray(children) || children.length < 2) return false; + return ( + children[0].controlType && children[0].controlType === "KEYVALUE_ARRAY" + ); + }; + + renderKVArray = (children: Array) => { + try { + // setup config for each child + children.forEach((c) => this.setupConfig(c)); + // We pass last child for legacy reasons, to keep the logic here exactly same as before. + return this.renderSingleConfig(children[children.length - 1], children); + } catch (e) { + log.error(e); + } + }; + + renderEachConfig = (section: any) => { + return ( +
+ {_.map(section.children, (propertyControlOrSection: ControlProps) => { + // If the section is hidden, skip rendering + if (isHidden(this.props.formData, section.hidden)) return null; + if ("children" in propertyControlOrSection) { + const { children } = propertyControlOrSection; + if (this.isKVArray(children)) { + return this.renderKVArray(children); + } + return this.renderEachConfig(propertyControlOrSection); + } else { + return this.renderSingleConfig(propertyControlOrSection); + } + })} +
+ ); + }; +} diff --git a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx index cce7f1d2e8..668bee65b1 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx @@ -8,10 +8,10 @@ import FormTitle from "./FormTitle"; import Button from "components/editorComponents/Button"; import { Datasource } from "entities/Datasource"; import { - reduxForm, - InjectedFormProps, - getFormValues, getFormMeta, + getFormValues, + InjectedFormProps, + reduxForm, } from "redux-form"; import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import AnalyticsUtil from "utils/AnalyticsUtil"; @@ -24,7 +24,7 @@ import DropDownControl from "components/formControls/DropDownControl"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import { connect } from "react-redux"; import { AppState } from "reducers"; -import { ApiActionConfig } from "entities/Action"; +import { ApiActionConfig, PluginType } from "entities/Action"; import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import { Toaster } from "components/ads/Toast"; import { Variant } from "components/ads/common"; @@ -46,10 +46,10 @@ import { GrantType, } from "entities/Datasource/RestAPIForm"; import { - REST_API_AUTHORIZATION_SUCCESSFUL, - REST_API_AUTHORIZATION_FAILED, - REST_API_AUTHORIZATION_APPSMITH_ERROR, createMessage, + REST_API_AUTHORIZATION_APPSMITH_ERROR, + REST_API_AUTHORIZATION_FAILED, + REST_API_AUTHORIZATION_SUCCESSFUL, } from "constants/messages"; import Collapsible from "./Collapsible"; import _ from "lodash"; @@ -233,11 +233,10 @@ class DatasourceRestAPIEditor extends React.Component { return true; }; - disableSave = () => { + disableSave = (): boolean => { const { formData } = this.props; if (!formData) return true; - if (!formData.url) return true; - return false; + return !formData.url; }; save = (onSuccess?: ReduxAction) => { @@ -632,7 +631,9 @@ class DatasourceRestAPIEditor extends React.Component { - this.save(redirectAuthorizationCode(pageId, datasourceId)) + this.save( + redirectAuthorizationCode(pageId, datasourceId, PluginType.API), + ) } text={isAuthorized ? "Save and Re-Authorize" : "Save and Authorize"} intent="primary" diff --git a/app/client/src/pages/Editor/DataSourceEditor/index.tsx b/app/client/src/pages/Editor/DataSourceEditor/index.tsx index 414f6ad291..a64034b027 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/index.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/index.tsx @@ -4,7 +4,6 @@ import { getFormValues, submit } from "redux-form"; import { AppState } from "reducers"; import _ from "lodash"; import { - getPluginPackageFromId, getPluginImages, getDatasource, getPlugin, @@ -23,10 +22,11 @@ import RestAPIDatasourceForm from "./RestAPIDatasourceForm"; import { Datasource } from "entities/Datasource"; import { RouteComponentProps } from "react-router"; import EntityNotFoundPane from "pages/Editor/EntityNotFoundPane"; +import { ReduxAction } from "constants/ReduxActionConstants"; +import { SAAS_EDITOR_DATASOURCE_ID_URL } from "../SaaSEditor/constants"; interface ReduxStateProps { formData: Datasource; - selectedPluginPackage: string; isSaving: boolean; isTesting: boolean; formConfig: any[]; @@ -37,6 +37,7 @@ interface ReduxStateProps { viewMode: boolean; pluginType: string; pluginDatasourceForm: string; + pluginPackageName: string; } type Props = ReduxStateProps & @@ -75,7 +76,6 @@ class DataSourceEditor extends React.Component { match: { params: { datasourceId }, }, - selectedPluginPackage, isSaving, formData, isTesting, @@ -102,7 +102,6 @@ class DataSourceEditor extends React.Component { onSubmit={this.handleSubmit} onSave={this.handleSave} onTest={this.props.testDatasource} - selectedPluginPackage={selectedPluginPackage} datasourceId={datasourceId} formData={formData} formConfig={formConfig} @@ -110,6 +109,7 @@ class DataSourceEditor extends React.Component { viewMode={viewMode} setDatasourceEditorMode={setDatasourceEditorMode} pluginType={pluginType} + formName={DATASOURCE_DB_FORM} /> ); } @@ -127,10 +127,6 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => { pluginImages: getPluginImages(state), formData, pluginId, - selectedPluginPackage: getPluginPackageFromId( - state, - datasourcePane.selectedPlugin, - ), isSaving: datasources.loading, isDeleting: datasources.isDeleting, isTesting: datasources.isTesting, @@ -140,13 +136,14 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => { viewMode: datasourcePane.viewMode[datasource?.id ?? ""] ?? true, pluginType: plugin?.type ?? "", pluginDatasourceForm: plugin?.datasourceComponent ?? "AutoForm", + pluginPackageName: plugin?.packageName ?? "", }; }; const mapDispatchToProps = (dispatch: any): DatasourcePaneFunctions => ({ submitForm: (name: string) => dispatch(submit(name)), - updateDatasource: (formData: any) => { - dispatch(updateDatasource(formData)); + updateDatasource: (formData: any, onSuccess?: ReduxAction) => { + dispatch(updateDatasource(formData, onSuccess)); }, testDatasource: (data: Datasource) => dispatch(testDatasource(data)), deleteDatasource: (id: string) => dispatch(deleteDatasource({ id })), @@ -157,7 +154,7 @@ const mapDispatchToProps = (dispatch: any): DatasourcePaneFunctions => ({ export interface DatasourcePaneFunctions { submitForm: (name: string) => void; - updateDatasource: (data: Datasource) => void; + updateDatasource: (formData: any, onSuccess?: ReduxAction) => void; testDatasource: (data: Datasource) => void; deleteDatasource: (id: string) => void; switchDatasource: (id: string) => void; @@ -178,6 +175,7 @@ class DatasourceEditorRouter extends React.Component { pluginImages, pluginId, pluginDatasourceForm, + pluginPackageName, } = this.props; if (!pluginId && datasourceId) { return ; @@ -209,6 +207,17 @@ class DatasourceEditorRouter extends React.Component { /> ); } + if (pluginDatasourceForm === "SaaSDatasourceForm") { + history.push( + SAAS_EDITOR_DATASOURCE_ID_URL( + applicationId, + pageId, + pluginPackageName, + datasourceId, + ), + ); + return; + } // Default to old flow // Todo: later refactor to make this "AutoForm" diff --git a/app/client/src/pages/Editor/Explorer/Actions/ActionsGroup.tsx b/app/client/src/pages/Editor/Explorer/Actions/ActionsGroup.tsx index 555e7d8790..16a3402f42 100644 --- a/app/client/src/pages/Editor/Explorer/Actions/ActionsGroup.tsx +++ b/app/client/src/pages/Editor/Explorer/Actions/ActionsGroup.tsx @@ -17,10 +17,12 @@ type ExplorerActionsGroupProps = { export const ExplorerActionsGroup = memo((props: ExplorerActionsGroupProps) => { const params = useParams(); const childNode: ReactNode = props.actions.map((action: any) => { + const plugin = props.plugins[action.config.pluginId]; const url = props.config?.getURL( params.applicationId, props.page.pageId, action.config.id, + plugin, ); const actionId = getActionIdFromURL(); const active = actionId === action.config.id; diff --git a/app/client/src/pages/Editor/Explorer/Actions/helpers.tsx b/app/client/src/pages/Editor/Explorer/Actions/helpers.tsx index 665f7811bf..11e7853cac 100644 --- a/app/client/src/pages/Editor/Explorer/Actions/helpers.tsx +++ b/app/client/src/pages/Editor/Explorer/Actions/helpers.tsx @@ -15,6 +15,10 @@ import { ExplorerURLParams } from "../helpers"; import { Datasource } from "entities/Datasource"; import { Plugin } from "api/PluginApi"; import PluginGroup from "../PluginGroup/PluginGroup"; +import { + SAAS_BASE_URL, + SAAS_EDITOR_API_ID_URL, +} from "pages/Editor/SaaSEditor/constants"; import { useSelector } from "react-redux"; import { AppState } from "reducers"; import { groupBy } from "lodash"; @@ -23,10 +27,15 @@ import { getNextEntityName } from "utils/AppsmithUtils"; export type ActionGroupConfig = { groupName: string; - type: PluginType; + types: PluginType[]; icon: JSX.Element; key: string; - getURL: (applicationId: string, pageId: string, id: string) => string; + getURL: ( + applicationId: string, + pageId: string, + id: string, + plugin?: Plugin, + ) => string; generateCreatePageURL: ( applicationId: string, pageId: string, @@ -47,13 +56,28 @@ export const ACTION_PLUGIN_MAP: Array< case PluginType.API: return { groupName: "APIs", - type, + types: [PluginType.API, PluginType.SAAS], icon: apiIcon, key: generateReactKey(), - getURL: (applicationId: string, pageId: string, id: string) => { - return `${API_EDITOR_ID_URL(applicationId, pageId, id)}`; + getURL: ( + applicationId: string, + pageId: string, + id: string, + plugin?: Plugin, + ) => { + if (!plugin || plugin.type === PluginType.API) { + return `${API_EDITOR_ID_URL(applicationId, pageId, id)}`; + } + return `${SAAS_EDITOR_API_ID_URL( + applicationId, + pageId, + plugin.packageName, + id, + )}`; }, - getIcon: (action: any) => { + getIcon: (action: any, plugin: Plugin) => { + if (plugin && plugin.type === PluginType.SAAS && plugin.iconLocation) + return ; const method = action.actionConfiguration.httpMethod; if (!method) return apiIcon; @@ -61,17 +85,22 @@ export const ACTION_PLUGIN_MAP: Array< }, generateCreatePageURL: API_EDITOR_URL_WITH_SELECTED_PAGE_ID, isGroupActive: (params: ExplorerURLParams, pageId: string) => - window.location.pathname === - API_EDITOR_URL(params.applicationId, pageId), + [ + API_EDITOR_URL(params.applicationId, pageId), + SAAS_BASE_URL(params.applicationId, pageId), + ].includes(window.location.pathname), isGroupExpanded: (params: ExplorerURLParams, pageId: string) => window.location.pathname.indexOf( API_EDITOR_URL(params.applicationId, pageId), + ) > -1 || + window.location.pathname.indexOf( + SAAS_BASE_URL(params.applicationId, pageId), ) > -1, }; case PluginType.DB: return { groupName: "DB Queries", - type, + types: [PluginType.DB], icon: dbQueryIcon, key: generateReactKey(), getURL: (applicationId: string, pageId: string, id: string) => @@ -96,9 +125,8 @@ export const ACTION_PLUGIN_MAP: Array< }); export const getActionConfig = (type: PluginType) => - ACTION_PLUGIN_MAP.find( - (configByType: ActionGroupConfig | undefined) => - configByType?.type === type, + ACTION_PLUGIN_MAP.find((configByType: ActionGroupConfig | undefined) => + configByType?.types.includes(type), ); export const getPluginGroups = ( @@ -113,13 +141,14 @@ export const getPluginGroups = ( return actionPluginMap?.map((config?: ActionGroupConfig) => { if (!config) return null; - const entries = actions?.filter( - (entry: any) => entry.config.pluginType === config?.type, + const entries = actions?.filter((entry: any) => + config.types.includes(entry.config.pluginType), ); - const filteredPlugins = plugins.filter( - (plugin) => plugin.type === config.type, + const filteredPlugins = plugins.filter((plugin) => + config.types.includes(plugin.type), ); + const filteredPluginIds = filteredPlugins.map((plugin) => plugin.id); const filteredDatasources = datasources.filter((datasource) => { return filteredPluginIds.includes(datasource.pluginId); @@ -135,7 +164,7 @@ export const getPluginGroups = ( return ( (); const dispatch = useDispatch(); const icon = getPluginIcon(props.plugin); - const switchDatasource = useCallback( - () => + const switchDatasource = useCallback(() => { + if (props.plugin && props.plugin.type === PluginType.SAAS) { + history.push( + SAAS_EDITOR_DATASOURCE_ID_URL( + params.applicationId, + params.pageId, + props.plugin.packageName, + props.datasource.id, + ), + ); + } else { history.push( DATA_SOURCES_EDITOR_ID_URL( params.applicationId, params.pageId, props.datasource.id, ), - ), - [params.applicationId, params.pageId, props.datasource.id], - ); + ); + } + }, [params.applicationId, params.pageId, props.datasource.id]); const queryId = getQueryIdFromURL(); const queryAction = useSelector((state: AppState) => diff --git a/app/client/src/pages/Editor/Explorer/Onboarding/DBQueryGroup.tsx b/app/client/src/pages/Editor/Explorer/Onboarding/DBQueryGroup.tsx index a6402457f6..4655694fbb 100644 --- a/app/client/src/pages/Editor/Explorer/Onboarding/DBQueryGroup.tsx +++ b/app/client/src/pages/Editor/Explorer/Onboarding/DBQueryGroup.tsx @@ -44,8 +44,8 @@ const DBQueryGroup = (props: any) => { const actions = useActions(""); const datasources = useFilteredDatasources(""); const plugins = useSelector(getPlugins); - const dbPluginMap = ACTION_PLUGIN_MAP.filter( - (plugin) => plugin?.type === PluginType.DB, + const dbPluginMap = ACTION_PLUGIN_MAP.filter((plugin) => + plugin?.types.includes(PluginType.DB), ); const addedWidget = useSelector( (state: AppState) => state.ui.onBoarding.addedWidget, diff --git a/app/client/src/pages/Editor/Explorer/PluginGroup/PluginGroup.tsx b/app/client/src/pages/Editor/Explorer/PluginGroup/PluginGroup.tsx index b1c62ba960..82920de11e 100644 --- a/app/client/src/pages/Editor/Explorer/PluginGroup/PluginGroup.tsx +++ b/app/client/src/pages/Editor/Explorer/PluginGroup/PluginGroup.tsx @@ -54,7 +54,7 @@ const ExplorerPluginGroup = memo((props: ExplorerPluginGroupProps) => { return ( { + it("getsApiId", () => { + window.history.pushState( + {}, + "Api", + "/applications/appId/pages/pageId/edit/api/apiId", + ); + const response = getActionIdFromURL(); + expect(response).toBe("apiId"); + }); + it("getsQueryId", () => { + window.history.pushState( + {}, + "Query", + "/applications/appId/pages/pageId/edit/queries/queryId", + ); + const response = getActionIdFromURL(); + expect(response).toBe("queryId"); + }); + + it("getsSaaSActionId", () => { + window.history.pushState( + {}, + "Query", + "/applications/appId/pages/pageId/edit/saas/google-sheets-plugin/api/saasActionId", + ); + const response = getActionIdFromURL(); + expect(response).toBe("saasActionId"); + }); +}); diff --git a/app/client/src/pages/Editor/Explorer/helpers.tsx b/app/client/src/pages/Editor/Explorer/helpers.tsx index 08c9c37ec1..08b7ceced2 100644 --- a/app/client/src/pages/Editor/Explorer/helpers.tsx +++ b/app/client/src/pages/Editor/Explorer/helpers.tsx @@ -5,6 +5,10 @@ import { QUERIES_EDITOR_ID_URL, DATA_SOURCES_EDITOR_ID_URL, } from "constants/routes"; +import { + SAAS_EDITOR_API_ID_URL, + SAAS_EDITOR_DATASOURCE_ID_URL, +} from "../SaaSEditor/constants"; export const ContextMenuPopoverModifiers: IPopoverSharedProps["modifiers"] = { offset: { enabled: true, @@ -38,6 +42,12 @@ export const getActionIdFromURL = () => { if (match?.params?.queryId) { return match.params.queryId; } + const saasMatch = matchPath<{ apiId: string }>(window.location.pathname, { + path: SAAS_EDITOR_API_ID_URL(), + }); + if (saasMatch?.params?.apiId) { + return saasMatch.params.apiId; + } }; export const getQueryIdFromURL = () => { @@ -56,4 +66,13 @@ export const getDatasourceIdFromURL = () => { if (match?.params?.datasourceId) { return match.params.datasourceId; } + const saasMatch = matchPath<{ datasourceId: string }>( + window.location.pathname, + { + path: SAAS_EDITOR_DATASOURCE_ID_URL(), + }, + ); + if (saasMatch?.params?.datasourceId) { + return saasMatch.params.datasourceId; + } }; diff --git a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx new file mode 100644 index 0000000000..5f954a3c51 --- /dev/null +++ b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx @@ -0,0 +1,580 @@ +import React from "react"; +import { InjectedFormProps } from "redux-form"; +import styled, { createGlobalStyle } from "styled-components"; +import { Icon, Popover, Tag } from "@blueprintjs/core"; +import { + components, + MenuListComponentProps, + OptionProps, + OptionTypeBase, + SingleValueProps, +} from "react-select"; +import { isString, isArray } from "lodash"; +import Button from "components/editorComponents/Button"; +import FormRow from "components/editorComponents/FormRow"; +import DropdownField from "components/editorComponents/form/fields/DropdownField"; +import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; +import { Datasource } from "entities/Datasource"; +import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView"; +import { Colors } from "constants/Colors"; +import JSONViewer from "./JSONViewer"; +import FormControl from "../FormControl"; +import Table from "./Table"; +import { Action } from "entities/Action"; +import { useDispatch } from "react-redux"; +import ActionNameEditor from "components/editorComponents/ActionNameEditor"; +import { ControlProps } from "components/formControls/BaseControl"; +import ActionSettings from "pages/Editor/ActionSettings"; +import { addTableWidgetFromQuery } from "actions/widgetActions"; +import { OnboardingStep } from "constants/OnboardingConstants"; +import Boxed from "components/editorComponents/Onboarding/Boxed"; +import OnboardingIndicator from "components/editorComponents/Onboarding/Indicator"; +import log from "loglevel"; + +const QueryFormContainer = styled.form` + display: flex; + flex-direction: column; + padding: 20px 0px; + width: 100%; + height: calc(100vh - ${(props) => props.theme.smallHeaderHeight}); + a { + font-size: 14px; + line-height: 20px; + margin-top: 15px; + } + .statementTextArea { + font-size: 14px; + line-height: 20px; + color: #2e3d49; + margin-top: 5px; + } + + .queryInput { + max-width: 30%; + padding-right: 10px; + } + span.bp3-popover-target { + display: initial !important; + } + + .executeOnLoad { + display: flex; + justify-content: flex-end; + margin-top: 10px; + } +`; + +const ActionsWrapper = styled.div` + display: flex; + align-items: center; + flex: 1 1 50%; + justify-content: flex-end; +`; + +const ActionButton = styled(BaseButton)` + &&&& { + min-width: 72px; + width: auto; + margin: 0 5px; + min-height: 30px; + } +`; + +const DropdownSelect = styled.div` + font-size: 14px; + margin-right: 10px; +`; + +const NoDataSourceContainer = styled.div` + align-items: center; + display: flex; + flex-direction: column; + margin-top: 62px; + flex: 1; + .font18 { + width: 50%; + text-align: center; + margin-bottom: 23px; + font-size: 18px; + color: #2e3d49; + } +`; + +const TooltipStyles = createGlobalStyle` + .helper-tooltip{ + width: 378px; + .bp3-popover { + height: 137px; + max-width: 378px; + box-shadow: none; + display: inherit !important; + .bp3-popover-arrow { + display: block; + fill: none; + } + .bp3-popover-arrow-fill { + fill: #23292E; + } + .bp3-popover-content { + padding: 15px; + background-color: #23292E; + color: #fff; + text-align: left; + border-radius: 4px; + text-transform: initial; + font-weight: 500; + font-size: 16px; + line-height: 20px; + } + .popoverBtn { + float: right; + margin-top: 25px; + } + .popuptext { + padding-right: 30px; + } + } + } +`; + +const ErrorMessage = styled.p` + font-size: 14px; + color: ${Colors.RED}; + display: inline-block; + margin-right: 10px; +`; +const CreateDatasource = styled.div` + height: 44px; + display: flex; + align-items: center; + justify-content: center; + font-weight: 500; + border-top: 1px solid ${Colors.ATHENS_GRAY}; + :hover { + cursor: pointer; + } + + .createIcon { + margin-right: 6px; + } +`; + +const Container = styled.div` + display: flex; + flex-direction: row; + align-items: center; + + .plugin-image { + height: 20px; + width: auto; + } + + .selected-value { + overflow: hidden; + text-overflow: ellipsis; + white-space: no-wrap; + margin-left: 6px; + } +`; + +const StyledOpenDocsIcon = styled(Icon)` + svg { + width: 12px; + height: 18px; + } +`; + +const NameWrapper = styled.div` + display: flex; + justify-content: space-between; + input { + margin: 0; + box-sizing: border-box; + } +`; + +const TabContainerView = styled.div` + .react-tabs__tab-panel { + overflow: scroll; + } + .react-tabs__tab-list { + margin: 0px; + } + &&& { + ul.react-tabs__tab-list { + padding-left: 23px; + } + } + position: relative; + margin-top: 31px; + height: calc(100vh - 150px); +`; + +const SettingsWrapper = styled.div` + padding: 5px 23px; +`; + +const AddWidgetButton = styled(BaseButton)` + &&&& { + height: 36px; + max-width: 125px; + border: 1px solid ${Colors.GEYSER_LIGHT}; + } +`; + +const OutputHeader = styled.div` + flex-direction: row; + justify-content: space-between; + display: flex; + margin: 10px 0px; + align-items: center; +`; + +const FieldWrapper = styled.div` + margin-top: 15px; +`; + +const StyledFormRow = styled(FormRow)` + padding: 0px 24px; + flex: 0; +`; + +const DocumentationLink = styled.a` + position: absolute; + right: 23px; + top: -6px; +`; + +const OutputWrapper = styled.div``; + +type QueryFormProps = { + onDeleteClick: () => void; + onRunClick: () => void; + onCreateDatasourceClick: () => void; + isDeleting: boolean; + isRunning: boolean; + dataSources: Datasource[]; + DATASOURCES_OPTIONS: any; + executedQueryData?: { + body: any; + isExecutionSuccess?: boolean; + }; + runErrorMessage: string | undefined; + location: { + state: any; + }; + editorConfig?: any; + formName: string; + settingConfig: any; +}; + +type ReduxProps = { + actionName: string; + responseType: string | undefined; + pluginId: string; + documentationLink: string | undefined; +}; + +export type EditorJSONtoFormProps = QueryFormProps & ReduxProps; + +type Props = EditorJSONtoFormProps & + InjectedFormProps; + +export const EditorJSONtoForm: React.FC = (props: Props) => { + const { + handleSubmit, + isDeleting, + isRunning, + onRunClick, + onDeleteClick, + onCreateDatasourceClick, + DATASOURCES_OPTIONS, + dataSources, + executedQueryData, + runErrorMessage, + responseType, + documentationLink, + editorConfig, + settingConfig, + formName, + actionName, + } = props; + + let error = runErrorMessage; + let output: Record[] | null = null; + let displayMessage = ""; + + if (executedQueryData) { + if (!executedQueryData.isExecutionSuccess) { + error = String(executedQueryData.body); + } else if (isString(executedQueryData.body)) { + output = JSON.parse(executedQueryData.body); + } else { + output = executedQueryData.body; + } + } + + // Constructing the header of the response based on the response + if (!output) { + displayMessage = "No data records to display"; + } else if (isArray(output)) { + // The returned output is an array + displayMessage = output.length + ? "Query response" + : "No data records to display"; + } else { + // Output is a JSON object. We can display a single object + displayMessage = "Query response"; + } + + const isTableResponse = responseType === "TABLE"; + + const dispatch = useDispatch(); + const onAddWidget = () => { + dispatch(addTableWidgetFromQuery(actionName)); + }; + + const MenuList = (props: MenuListComponentProps<{ children: Node }>) => { + return ( + <> + {props.children} + onCreateDatasourceClick()}> + + Create new datasource + + + ); + }; + + const SingleValue = (props: SingleValueProps) => { + return ( + <> + + + Datasource +
{props.children}
+
+
+ + ); + }; + + const CustomOption = (props: OptionProps) => { + return ( + <> + + + Datasource +
{props.children}
+
+
+ + ); + }; + + return ( + + + + + + + + + + + {dataSources.length === 0 ? ( + <> + + + +
+

+ You don’t have a Data Source to run this query +

+
+
+ + ) : ( + + + + )} +
+
+ + {documentationLink && ( + + {"Documentation "} + + + )} + + {editorConfig && editorConfig.length > 0 ? ( + editorConfig.map(renderEachConfig(formName)) + ) : ( + <> + An unexpected error occurred + window.location.reload()} + > + Refresh + + + )} + {dataSources.length === 0 && ( + +

+ Seems like you don’t have any Datasources to create a + query +

+