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}
+
+
+ ))}
{/* 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 (
+ <>
+
+
+
+ {props.children}
+
+
+ >
+ );
+ };
+
+ const CustomOption = (props: OptionProps) => {
+ return (
+ <>
+
+
+
+ {props.children}
+
+
+ >
+ );
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {dataSources.length === 0 ? (
+ <>
+
+
+
+
+
+ You don’t have a Data Source to run this query
+
+
onCreateDatasourceClick()}
+ text="Add Datasource"
+ intent="primary"
+ filled
+ size="small"
+ className="popoverBtn"
+ />
+
+
+ >
+ ) : (
+
+
+
+ )}
+
+
+
+ {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
+
+ onCreateDatasourceClick()}
+ text="Add a Datasource"
+ intent="primary"
+ filled
+ size="small"
+ icon="plus"
+ />
+
+ )}
+
+ {error && (
+
+ Query error
+ {error}
+
+ )}
+
+ {!error && output && dataSources.length && (
+
+
+ {displayMessage}
+ {!!output.length && (
+
+
+
+ )}
+
+ {isTableResponse ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+ ),
+ },
+ {
+ key: "settings",
+ title: "Settings",
+ panelComponent: (
+
+
+
+ ),
+ },
+ ]}
+ />
+
+
+ );
+};
+
+const renderEachConfig = (formName: string) => (section: any): any => {
+ return section.children.map((formControlOrSection: ControlProps) => {
+ if ("children" in formControlOrSection) {
+ return renderEachConfig(formName)(formControlOrSection);
+ } else {
+ try {
+ const { configProperty } = formControlOrSection;
+ return (
+
+
+
+ );
+ } catch (e) {
+ log.error(e);
+ }
+ }
+ return null;
+ });
+};
diff --git a/app/client/src/pages/Editor/QueryEditor/Form.tsx b/app/client/src/pages/Editor/QueryEditor/Form.tsx
index faa89adac9..11ce320b87 100644
--- a/app/client/src/pages/Editor/QueryEditor/Form.tsx
+++ b/app/client/src/pages/Editor/QueryEditor/Form.tsx
@@ -1,605 +1,14 @@
-import React from "react";
-import { formValueSelector, InjectedFormProps, reduxForm } 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 history from "utils/history";
-import { DATA_SOURCES_EDITOR_URL } from "constants/routes";
-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 { formValueSelector, reduxForm } from "redux-form";
import { QUERY_EDITOR_FORM_NAME } from "constants/forms";
-import { Colors } from "constants/Colors";
-import JSONViewer from "./JSONViewer";
-import FormControl from "../FormControl";
-import Table from "./Table";
import { Action } from "entities/Action";
-import { connect, useDispatch } from "react-redux";
+import { connect } from "react-redux";
import { AppState } from "reducers";
-import ActionNameEditor from "components/editorComponents/ActionNameEditor";
import {
getPluginResponseTypes,
getPluginDocumentationLinks,
} from "selectors/entitiesSelector";
-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;
- isDeleting: boolean;
- isRunning: boolean;
- dataSources: Datasource[];
- DATASOURCES_OPTIONS: any;
- executedQueryData: {
- body: Record[] | string;
- isExecutionSuccess: boolean;
- };
- applicationId: string;
- runErrorMessage: string | undefined;
- pageId: string;
- location: {
- state: any;
- };
- editorConfig?: any;
- settingConfig: any;
-};
-
-type ReduxProps = {
- actionName: string;
- responseType: string | undefined;
- pluginId: string;
- documentationLink: string | undefined;
-};
-
-export type StateAndRouteProps = QueryFormProps & ReduxProps;
-
-type Props = StateAndRouteProps & InjectedFormProps;
-
-const QueryEditorForm: React.FC = (props: Props) => {
- const {
- handleSubmit,
- isDeleting,
- isRunning,
- onRunClick,
- onDeleteClick,
- DATASOURCES_OPTIONS,
- pageId,
- applicationId,
- dataSources,
- executedQueryData,
- runErrorMessage,
- responseType,
- documentationLink,
- editorConfig,
- 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}
- {
- history.push(DATA_SOURCES_EDITOR_URL(applicationId, pageId));
- }}
- >
-
- Create new datasource
-
- >
- );
- };
-
- const SingleValue = (props: SingleValueProps) => {
- return (
- <>
-
-
-
- {props.children}
-
-
- >
- );
- };
-
- const CustomOption = (props: OptionProps) => {
- return (
- <>
-
-
-
- {props.children}
-
-
- >
- );
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
- {dataSources.length === 0 ? (
- <>
-
-
-
-
-
- You don’t have a Data Source to run this query
-
-
- history.push(
- DATA_SOURCES_EDITOR_URL(applicationId, pageId),
- )
- }
- text="Add Datasource"
- intent="primary"
- filled
- size="small"
- className="popoverBtn"
- />
-
-
- >
- ) : (
-
-
-
- )}
-
-
-
- {documentationLink && (
-
- {"Documentation "}
-
-
- )}
-
- {editorConfig && editorConfig.length > 0 ? (
- editorConfig.map(renderEachConfig)
- ) : (
- <>
- An unexpected error occurred
- window.location.reload()}
- >
- Refresh
-
- >
- )}
- {dataSources.length === 0 && (
-
-
- Seems like you don’t have any Datasources to create a
- query
-
-
- history.push(
- DATA_SOURCES_EDITOR_URL(applicationId, pageId),
- )
- }
- text="Add a Datasource"
- intent="primary"
- filled
- size="small"
- icon="plus"
- />
-
- )}
-
- {error && (
-
- Query error
- {error}
-
- )}
-
- {!error && output && dataSources.length && (
-
-
- {displayMessage}
- {!!output.length && (
-
-
-
- )}
-
- {isTableResponse ? (
-
- ) : (
-
- )}
-
- )}
-
- ),
- },
- {
- key: "settings",
- title: "Settings",
- panelComponent: (
-
-
-
- ),
- },
- ]}
- />
-
-
- );
-};
-
-const renderEachConfig = (section: any): any => {
- return section.children.map((formControlOrSection: ControlProps) => {
- if ("children" in formControlOrSection) {
- return renderEachConfig(formControlOrSection);
- } else {
- try {
- const { configProperty } = formControlOrSection;
- return (
-
-
-
- );
- } catch (e) {
- log.error(e);
- }
- }
- return null;
- });
-};
+import { EditorJSONtoForm, EditorJSONtoFormProps } from "./EditorJSONtoForm";
const valueSelector = formValueSelector(QUERY_EDITOR_FORM_NAME);
const mapStateToProps = (state: AppState) => {
@@ -613,12 +22,13 @@ const mapStateToProps = (state: AppState) => {
pluginId,
responseType: responseTypes[pluginId],
documentationLink: documentationLinks[pluginId],
+ formName: QUERY_EDITOR_FORM_NAME,
};
};
export default connect(mapStateToProps)(
- reduxForm({
+ reduxForm({
form: QUERY_EDITOR_FORM_NAME,
enableReinitialize: true,
- })(QueryEditorForm),
+ })(EditorJSONtoForm),
);
diff --git a/app/client/src/pages/Editor/QueryEditor/index.tsx b/app/client/src/pages/Editor/QueryEditor/index.tsx
index 90d4aac1a8..f6b6a50d46 100644
--- a/app/client/src/pages/Editor/QueryEditor/index.tsx
+++ b/app/client/src/pages/Editor/QueryEditor/index.tsx
@@ -3,7 +3,11 @@ import { RouteComponentProps } from "react-router";
import { connect } from "react-redux";
import { getFormValues } from "redux-form";
import styled from "styled-components";
-import { QueryEditorRouteParams } from "constants/routes";
+import {
+ DATA_SOURCES_EDITOR_URL,
+ QueryEditorRouteParams,
+} from "constants/routes";
+import history from "utils/history";
import QueryEditorForm from "./Form";
import QueryHomeScreen from "./QueryHomeScreen";
import { deleteAction, runActionInit } from "actions/actionActions";
@@ -141,13 +145,15 @@ class QueryEditor extends React.Component {
value: dataSource.id,
image: pluginImages[dataSource.pluginId],
}));
+
+ const onCreateDatasourceClick = () => {
+ history.push(DATA_SOURCES_EDITOR_URL(applicationId, pageId));
+ };
return (
{queryId ? (
{
DATASOURCES_OPTIONS={DATASOURCES_OPTIONS}
executedQueryData={responses[queryId]}
runErrorMessage={runErrorMessage[queryId]}
+ onCreateDatasourceClick={onCreateDatasourceClick}
/>
) : (
& { plugin?: Plugin };
+
+type Props = StateAndRouteProps & InjectedFormProps;
+
+const ActionForm: React.FC = (props: Props) => {
+ const {
+ match: {
+ params: { pageId, applicationId, apiId },
+ },
+ actionName,
+ plugin,
+ } = props;
+
+ const dispatch = useDispatch();
+ const onDeleteClick = () => {
+ dispatch(deleteAction({ id: apiId, name: actionName }));
+ };
+
+ const onRunClick = () => {
+ dispatch(runActionInit(apiId));
+ };
+ const onCreateDatasourceClick = () => {
+ history.push(SAAS_EDITOR_URL(applicationId, pageId, plugin?.packageName));
+ };
+
+ const childProps: any = {
+ ...props,
+ onRunClick,
+ onDeleteClick,
+ onCreateDatasourceClick,
+ };
+ return ;
+};
+
+const mapStateToProps = (state: AppState, props: any) => {
+ const { apiId } = props.match.params;
+ const { runErrorMessage } = state.ui.queryPane;
+ const { plugins } = state.entities;
+ const { editorConfigs, settingConfigs } = plugins;
+ const pluginImages = getPluginImages(state);
+
+ const action = getAction(state, apiId);
+ const actionName = action?.name ?? "";
+ const pluginId = action?.pluginId ?? "";
+ const plugin = getPlugin(state, pluginId);
+ const responseTypes = getPluginResponseTypes(state);
+ const documentationLinks = getPluginDocumentationLinks(state);
+ let editorConfig: any;
+ const initialValues = {};
+ if (editorConfigs && pluginId) {
+ editorConfig = editorConfigs[pluginId];
+ if (editorConfig) {
+ merge(initialValues, getConfigInitialValues(editorConfig));
+ }
+ }
+ let settingConfig: any;
+
+ if (settingConfigs && pluginId) {
+ settingConfig = settingConfigs[pluginId];
+ }
+ merge(initialValues, getConfigInitialValues(settingConfig));
+ merge(initialValues, action);
+
+ const dataSources = getDatasourceByPluginId(state, pluginId);
+ const DATASOURCES_OPTIONS = dataSources.map((dataSource: Datasource) => ({
+ label: dataSource.name,
+ value: dataSource.id,
+ image: pluginImages[dataSource.pluginId],
+ }));
+
+ const responses = getActionResponses(state);
+ return {
+ isRunning: state.ui.queryPane.isRunning[apiId],
+ isDeleting: state.ui.queryPane.isDeleting[apiId],
+ editorConfig,
+ settingConfig,
+ actionName,
+ pluginId,
+ plugin,
+ responseType: responseTypes[pluginId],
+ formData: getFormValues(SAAS_EDITOR_FORM)(state) as SaaSAction,
+ documentationLink: documentationLinks[pluginId],
+ initialValues,
+ dataSources,
+ DATASOURCES_OPTIONS,
+ executedQueryData: responses[apiId],
+ runErrorMessage: runErrorMessage[apiId],
+ formName: SAAS_EDITOR_FORM,
+ };
+};
+
+export default connect(mapStateToProps)(
+ reduxForm({
+ form: SAAS_EDITOR_FORM,
+ enableReinitialize: true,
+ })(ActionForm),
+);
diff --git a/app/client/src/pages/Editor/SaaSEditor/DatasourceCard.tsx b/app/client/src/pages/Editor/SaaSEditor/DatasourceCard.tsx
new file mode 100644
index 0000000000..95037b467d
--- /dev/null
+++ b/app/client/src/pages/Editor/SaaSEditor/DatasourceCard.tsx
@@ -0,0 +1,160 @@
+import { Datasource } from "entities/Datasource";
+import { isStoredDatasource } from "entities/Action";
+import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
+import React from "react";
+import { isNil } from "lodash";
+import { useSelector } from "react-redux";
+import { Colors } from "constants/Colors";
+import { useParams } from "react-router";
+
+import {
+ getActionsForCurrentPage,
+ getPluginImages,
+} from "selectors/entitiesSelector";
+import styled from "styled-components";
+import { AppState } from "reducers";
+import history from "utils/history";
+
+import { renderDatasourceSection } from "pages/Editor/DataSourceEditor/DatasourceSection";
+import { SAAS_EDITOR_DATASOURCE_ID_URL } from "./constants";
+
+const Wrapper = styled.div`
+ border: 2px solid #d6d6d6;
+ padding: 18px;
+ margin-top: 18px;
+`;
+
+const ActionButton = styled(BaseButton)`
+ &&&& {
+ height: 36px;
+ max-width: 120px;
+ width: auto;
+ }
+`;
+
+const DatasourceImage = styled.img`
+ height: 24px;
+ width: auto;
+`;
+
+const EditDatasourceButton = styled(BaseButton)`
+ &&&& {
+ height: 36px;
+ max-width: 160px;
+ border: 1px solid ${Colors.GEYSER_LIGHT};
+ width: auto;
+ }
+`;
+
+const DatasourceName = styled.span`
+ margin-left: 10px;
+ font-size: 16px;
+ font-weight: 500;
+`;
+
+const DatasourceCardHeader = styled.div`
+ justify-content: space-between;
+ display: flex;
+`;
+
+const DatasourceNameWrapper = styled.div`
+ flex-direction: row;
+ align-items: center;
+ display: flex;
+`;
+
+const Queries = styled.div`
+ color: ${Colors.DOVE_GRAY};
+ font-size: 14px;
+ display: inline-block;
+ margin-top: 11px;
+`;
+
+const ButtonsWrapper = styled.div`
+ display: flex;
+ gap: 10px;
+`;
+
+type DatasourceCardProps = {
+ datasource: Datasource;
+ onCreate: (datasource: Datasource) => void;
+};
+
+// TODO: This is largely a quick copy pasta and edit of QueryEditor/DatasourceCard.tsx
+// When we move Google Sheets over to regular oauth2 integrations, we will need to refactor this.
+const DatasourceCard = (props: DatasourceCardProps) => {
+ const pluginImages = useSelector(getPluginImages);
+ const params = useParams<{
+ applicationId: string;
+ pageId: string;
+ pluginPackageName: string;
+ }>();
+ const { datasource } = props;
+ const datasourceFormConfigs = useSelector(
+ (state: AppState) => state.entities.plugins.formConfigs,
+ );
+ const queryActions = useSelector(getActionsForCurrentPage);
+ const queriesWithThisDatasource = queryActions.filter(
+ (action) =>
+ isStoredDatasource(action.config.datasource) &&
+ action.config.datasource.id === datasource.id,
+ ).length;
+
+ const currentFormConfig: Array =
+ datasourceFormConfigs[datasource?.pluginId ?? ""];
+ const QUERY = queriesWithThisDatasource > 1 ? "APIs" : "API";
+
+ const editDatasource = () => {
+ history.push(
+ SAAS_EDITOR_DATASOURCE_ID_URL(
+ params.applicationId,
+ params.pageId,
+ params.pluginPackageName,
+ datasource.id,
+ ),
+ );
+ };
+
+ return (
+
+
+
+
+
+ {datasource.name}
+
+
+ {queriesWithThisDatasource
+ ? `${queriesWithThisDatasource} ${QUERY} on this page`
+ : "No API is using this datasource"}
+
+
+
+
+ props.onCreate(datasource)}
+ />
+
+
+ {!isNil(currentFormConfig)
+ ? renderDatasourceSection(currentFormConfig[0], datasource)
+ : undefined}
+
+ );
+};
+
+export default DatasourceCard;
diff --git a/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx
new file mode 100644
index 0000000000..94680acf29
--- /dev/null
+++ b/app/client/src/pages/Editor/SaaSEditor/DatasourceForm.tsx
@@ -0,0 +1,278 @@
+import React from "react";
+import styled from "styled-components";
+import _, { merge } from "lodash";
+import { DATASOURCE_SAAS_FORM } from "constants/forms";
+import { SAAS_EDITOR_URL } from "./constants";
+import history from "utils/history";
+import FormTitle from "pages/Editor/DataSourceEditor/FormTitle";
+import Button from "components/editorComponents/Button";
+import { Datasource } from "entities/Datasource";
+import { getFormValues, InjectedFormProps, reduxForm } from "redux-form";
+import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
+import BackButton from "pages/Editor/DataSourceEditor/BackButton";
+import { RouteComponentProps } from "react-router";
+import { connect } from "react-redux";
+import { AppState } from "reducers";
+import { getDatasource, getPluginImages } from "selectors/entitiesSelector";
+import { ReduxAction } from "constants/ReduxActionConstants";
+import {
+ deleteDatasource,
+ getOAuthAccessToken,
+ redirectAuthorizationCode,
+ updateDatasource,
+} from "actions/datasourceActions";
+import { historyPush } from "actions/utilActions";
+import { createNewApiName } from "utils/AppsmithUtils";
+import { createActionRequest } from "actions/actionActions";
+import { ActionDataState } from "reducers/entityReducers/actionsReducer";
+import {
+ ActionButton,
+ FormTitleContainer,
+ Header,
+ JSONtoForm,
+ JSONtoFormProps,
+ PluginImage,
+ SaveButtonContainer,
+} from "../DataSourceEditor/JSONtoForm";
+import { getConfigInitialValues } from "components/formControls/utils";
+import {
+ SAAS_AUTHORIZATION_APPSMITH_ERROR,
+ SAAS_AUTHORIZATION_FAILED,
+} from "constants/messages";
+import { Variant } from "components/ads/common";
+import { Toaster } from "components/ads/Toast";
+import { PluginType } from "entities/Action";
+
+interface StateProps extends JSONtoFormProps {
+ isSaving: boolean;
+ isDeleting: boolean;
+ loadingFormConfigs: boolean;
+ isNewDatasource: boolean;
+ pluginImage: string;
+ pluginId: string;
+ actions: ActionDataState;
+}
+
+interface DispatchFunctions {
+ updateDatasource: (formData: any, onSuccess?: ReduxAction) => void;
+ deleteDatasource: (id: string, onSuccess?: ReduxAction) => void;
+ getOAuthAccessToken: (id: string) => void;
+}
+
+type DatasourceSaaSEditorProps = StateProps &
+ DispatchFunctions &
+ RouteComponentProps<{
+ datasourceId: string;
+ applicationId: string;
+ pageId: string;
+ pluginPackageName: string;
+ }>;
+
+type Props = DatasourceSaaSEditorProps &
+ InjectedFormProps;
+
+const StyledButton = styled(Button)`
+ &&&& {
+ width: 180px;
+ height: 32px;
+ }
+`;
+
+const CreateApiButton = styled(BaseButton)`
+ &&& {
+ max-width: 120px;
+ margin-right: 9px;
+ align-self: center;
+ min-height: 32px;
+ }
+`;
+
+class DatasourceSaaSEditor extends JSONtoForm {
+ componentDidMount() {
+ super.componentDidMount();
+ const search = new URLSearchParams(this.props.location.search);
+ const status = search.get("response_status");
+
+ if (status) {
+ const display_message = search.get("display_message");
+ // Set default error message
+ let message = SAAS_AUTHORIZATION_FAILED;
+ const variant = Variant.danger;
+ if (status !== "success") {
+ if (status === "appsmith_error") {
+ message = SAAS_AUTHORIZATION_APPSMITH_ERROR;
+ }
+ Toaster.show({ text: display_message || message, variant });
+ } else {
+ this.props.getOAuthAccessToken(this.props.match.params.datasourceId);
+ }
+ }
+ }
+
+ save = (onSuccess?: ReduxAction) => {
+ const normalizedValues = this.normalizeValues();
+ this.props.updateDatasource(normalizedValues, onSuccess);
+ };
+
+ createApiAction = () => {
+ const {
+ formData,
+ actions,
+ match: {
+ params: { pageId },
+ },
+ } = this.props;
+ const newApiName = createNewApiName(actions, pageId || "");
+
+ this.save(
+ createActionRequest({
+ name: newApiName,
+ pageId: pageId,
+ pluginId: formData.pluginId,
+ datasource: {
+ id: formData.id,
+ },
+ }),
+ );
+ };
+
+ render() {
+ const { formConfig } = this.props;
+ const content = this.renderDataSourceConfigForm(formConfig);
+ return this.renderForm(content);
+ }
+
+ renderDataSourceConfigForm = (sections: any) => {
+ const {
+ match: {
+ params: { applicationId, datasourceId, pageId, pluginPackageName },
+ },
+ isSaving,
+ isDeleting,
+ deleteDatasource,
+ } = this.props;
+
+ return (
+
+ );
+ };
+}
+
+const mapStateToProps = (state: AppState, props: any) => {
+ const { datasourcePane } = state.ui;
+ const { datasources, plugins } = state.entities;
+ const datasource = getDatasource(state, props.match.params.datasourceId);
+ const { formConfigs } = plugins;
+ const formData = getFormValues(DATASOURCE_SAAS_FORM)(state) as Datasource;
+ const pluginId = _.get(datasource, "pluginId", "");
+ const formConfig = formConfigs[pluginId];
+ const initialValues = {};
+ if (formConfig) {
+ merge(initialValues, getConfigInitialValues(formConfig));
+ }
+ merge(initialValues, datasource);
+ return {
+ isSaving: datasources.loading,
+ isDeleting: datasources.isDeleting,
+ formData: formData,
+ formConfig,
+ isNewDatasource:
+ datasourcePane.newDatasource === props.match.params.datasourceId,
+ pluginImage: getPluginImages(state)[pluginId],
+ initialValues,
+ pluginId: pluginId,
+ actions: state.entities.actions,
+ formName: DATASOURCE_SAAS_FORM,
+ };
+};
+
+const mapDispatchToProps = (dispatch: any): DispatchFunctions => {
+ return {
+ deleteDatasource: (id: string, onSuccess?: ReduxAction) =>
+ dispatch(deleteDatasource({ id }, onSuccess)),
+ updateDatasource: (formData: any, onSuccess?: ReduxAction) =>
+ dispatch(updateDatasource(formData, onSuccess)),
+ getOAuthAccessToken: (datasourceId: string) =>
+ dispatch(getOAuthAccessToken(datasourceId)),
+ };
+};
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(
+ reduxForm({
+ form: DATASOURCE_SAAS_FORM,
+ enableReinitialize: true,
+ })(DatasourceSaaSEditor),
+);
diff --git a/app/client/src/pages/Editor/SaaSEditor/ListView.tsx b/app/client/src/pages/Editor/SaaSEditor/ListView.tsx
new file mode 100644
index 0000000000..a6bd8e7dcd
--- /dev/null
+++ b/app/client/src/pages/Editor/SaaSEditor/ListView.tsx
@@ -0,0 +1,208 @@
+import React from "react";
+import { connect } from "react-redux";
+import { RouteComponentProps } from "react-router";
+import { Plugin } from "api/PluginApi";
+import {
+ getDatasourcesByPluginId,
+ getPluginByPackageName,
+} from "selectors/entitiesSelector";
+import NotFound from "pages/common/NotFound";
+import { AppState } from "reducers";
+import { createDatasourceFromForm } from "actions/datasourceActions";
+import { SaaSAction } from "entities/Action";
+import { createActionRequest } from "actions/actionActions";
+import { Datasource } from "entities/Datasource";
+import { createNewApiName } from "utils/AppsmithUtils";
+import { ActionDataState } from "reducers/entityReducers/actionsReducer";
+
+// Design
+import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
+import styled from "styled-components";
+import { Spinner, Button } from "@blueprintjs/core";
+import DatasourceCard from "pages/Editor/SaaSEditor/DatasourceCard";
+import { getIsEditorInitialized } from "selectors/editorSelectors";
+import { API_EDITOR_URL } from "constants/routes";
+
+const IntegrationHomePage = styled.div`
+ padding: 20px;
+ padding-top: 30px;
+ overflow: auto;
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - ${(props) => props.theme.smallHeaderHeight});
+
+ .sectionHeader {
+ font-weight: ${(props) => props.theme.fontWeights[2]};
+ font-size: ${(props) => props.theme.fontSizes[4]}px;
+ }
+`;
+
+const LoadingContainer = styled(CenteredWrapper)`
+ height: 50%;
+`;
+
+const AddDatasource = styled(Button)`
+ padding: 23px;
+ border: 2px solid #d6d6d6;
+ justify-content: flex-start;
+ font-size: 16px;
+ font-weight: 500;
+`;
+
+const Boundary = styled.hr`
+ border: 1px solid #d0d7dd;
+ margin-top: 16px;
+`;
+
+interface StateProps {
+ plugin?: Plugin;
+ actions: ActionDataState;
+ datasources: Datasource[];
+ isCreating: boolean;
+ isEditorInitialized: boolean;
+}
+
+interface DispatchFunctions {
+ createDatasource: (data: any) => void;
+ createAction: (data: Partial) => void;
+}
+
+type RouteProps = RouteComponentProps<{
+ applicationId: string;
+ pageId: string;
+ pluginPackageName: string;
+}>;
+
+type Props = StateProps & DispatchFunctions & RouteProps;
+class ListView extends React.Component {
+ handleCreateNewDatasource = (pluginId: string) => {
+ this.props.createDatasource({ pluginId });
+ };
+
+ handleCreateNewAPI = (datasource: Datasource) => {
+ const {
+ match: {
+ params: { pageId },
+ },
+ actions,
+ location,
+ } = this.props;
+ const params: string = location.search;
+ let pgId = new URLSearchParams(params).get("importTo");
+ if (!pgId) {
+ pgId = pageId;
+ }
+ if (pgId) {
+ const newApiName = createNewApiName(actions, pgId);
+
+ this.props.createAction({
+ name: newApiName,
+ pageId: pgId,
+ pluginId: datasource.pluginId,
+ datasource: {
+ id: datasource.id,
+ },
+ });
+ }
+ };
+
+ render() {
+ const { plugin, isCreating, isEditorInitialized } = this.props;
+ if (!plugin) {
+ return this.renderNotFound();
+ }
+ if (isCreating || !isEditorInitialized) {
+ return this.renderLoading();
+ }
+ return this.renderPage();
+ }
+
+ renderPage() {
+ const { datasources, plugin } = this.props;
+ if (!plugin) {
+ return this.renderNotFound();
+ }
+ return (
+
+ Select a datasource or create a new one
+
+
+ this.handleCreateNewDatasource(plugin.id)}
+ fill
+ minimal
+ text="New Datasource"
+ icon={"plus"}
+ />
+
+ {datasources.map((datasource) => {
+ return (
+
+ );
+ })}
+
+ );
+ }
+
+ renderLoading() {
+ return (
+
+
+
+ );
+ }
+
+ renderNotFound() {
+ const {
+ match: {
+ params: { applicationId, pageId },
+ },
+ history,
+ } = this.props;
+ return (
+
+
+ history.push(API_EDITOR_URL(applicationId, pageId))
+ }
+ />
+
+ );
+ }
+}
+
+const mapStateToProps = (state: AppState, props: RouteProps): StateProps => {
+ const plugin = getPluginByPackageName(
+ state,
+ props.match.params.pluginPackageName,
+ );
+ let datasources: Datasource[] = [];
+ if (plugin) {
+ datasources = getDatasourcesByPluginId(state, plugin.id);
+ }
+ return {
+ plugin,
+ actions: state.entities.actions,
+ isCreating: state.ui.apiPane.isCreating,
+ isEditorInitialized: getIsEditorInitialized(state),
+ datasources: datasources,
+ };
+};
+
+const mapDispatchToProps = (dispatch: any): DispatchFunctions => {
+ return {
+ createDatasource: (data: any) => dispatch(createDatasourceFromForm(data)),
+ createAction: (data: Partial) => {
+ dispatch(createActionRequest(data));
+ },
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps)(ListView);
diff --git a/app/client/src/pages/Editor/SaaSEditor/constants.ts b/app/client/src/pages/Editor/SaaSEditor/constants.ts
new file mode 100644
index 0000000000..3d37082a49
--- /dev/null
+++ b/app/client/src/pages/Editor/SaaSEditor/constants.ts
@@ -0,0 +1,46 @@
+import { BUILDER_PAGE_URL, convertToQueryParams } from "constants/routes";
+
+export const SAAS_BASE_URL = (
+ applicationId = ":applicationId",
+ pageId = ":pageId",
+) => `${BUILDER_PAGE_URL(applicationId, pageId)}/saas`;
+
+export const SAAS_EDITOR_URL = (
+ applicationId = ":applicationId",
+ pageId = ":pageId",
+ pluginPackageName = ":pluginPackageName",
+): string => {
+ return `${SAAS_BASE_URL(applicationId, pageId)}/${pluginPackageName}`;
+};
+
+export const SAAS_EDITOR_DATASOURCE_ID_URL = (
+ applicationId = ":applicationId",
+ pageId = ":pageId",
+ pluginPackageName = ":pluginPackageName",
+ datasourceId = ":datasourceId",
+ params = {},
+): string => {
+ const queryParams = convertToQueryParams(params);
+ return `${SAAS_EDITOR_URL(
+ applicationId,
+ pageId,
+ pluginPackageName,
+ )}/datasources/${datasourceId}${queryParams}`;
+};
+
+export const SAAS_EDITOR_API_ID_URL = (
+ applicationId = ":applicationId",
+ pageId = ":pageId",
+ pluginPackageName = ":pluginPackageName",
+ apiId = ":apiId",
+ params = {},
+): string => {
+ const queryParams = convertToQueryParams(params);
+ return `${SAAS_EDITOR_URL(
+ applicationId,
+ pageId,
+ pluginPackageName,
+ )}/api/${apiId}${queryParams}`;
+};
+
+export const APPSMITH_TOKEN_STORAGE_KEY = "APPSMITH_AUTH_TOKEN";
diff --git a/app/client/src/pages/Editor/SaaSEditor/routes.ts b/app/client/src/pages/Editor/SaaSEditor/routes.ts
new file mode 100644
index 0000000000..3293e81c5f
--- /dev/null
+++ b/app/client/src/pages/Editor/SaaSEditor/routes.ts
@@ -0,0 +1,23 @@
+import {
+ SAAS_EDITOR_URL,
+ SAAS_EDITOR_DATASOURCE_ID_URL,
+ SAAS_EDITOR_API_ID_URL,
+} from "pages/Editor/SaaSEditor/constants";
+import ListView from "pages/Editor/SaaSEditor/ListView";
+import DatasourceForm from "pages/Editor/SaaSEditor/DatasourceForm";
+import ActionForm from "pages/Editor/SaaSEditor/ActionForm";
+
+export const SaaSEditorRoutes = [
+ {
+ path: SAAS_EDITOR_URL(),
+ component: ListView,
+ },
+ {
+ path: SAAS_EDITOR_DATASOURCE_ID_URL(),
+ component: DatasourceForm,
+ },
+ {
+ path: SAAS_EDITOR_API_ID_URL(),
+ component: ActionForm,
+ },
+];
diff --git a/app/client/src/pages/Editor/routes.tsx b/app/client/src/pages/Editor/routes.tsx
index d82ec490d4..652679ac79 100644
--- a/app/client/src/pages/Editor/routes.tsx
+++ b/app/client/src/pages/Editor/routes.tsx
@@ -40,6 +40,8 @@ import PerformanceTracker, {
import * as Sentry from "@sentry/react";
const SentryRoute = Sentry.withSentryRouting(Route);
+import { SaaSEditorRoutes } from "./SaaSEditor/routes";
+
const Wrapper = styled.div<{ isVisible: boolean }>`
position: absolute;
top: 0;
@@ -158,6 +160,9 @@ class EditorsRouter extends React.Component<
path={getCurlImportPageURL()}
component={CurlImportForm}
/>
+ {SaaSEditorRoutes.map((props) => (
+
+ ))}
props.theme.fontWeights[3]};
+ font-size: 24px;
+ }
+ .unavailable-img {
+ width: 35%;
+ }
+ .button-position {
+ margin: auto;
+ }
+`;
+
+interface Props {
+ title: string;
+ subtitle?: string;
+ buttonText: string;
+ onBackButton: () => void;
+}
+
+const NotFound: React.FC = (props: Props) => {
+ const { title, subtitle, buttonText, onBackButton } = props;
+
+ return (
+
+
+
+
{title}
+ {subtitle &&
{subtitle}
}
+
onBackButton()}
+ />
+
+
+ );
+};
+
+export default NotFound;
diff --git a/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts b/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts
index 3662ef3284..8cd2c3d60b 100644
--- a/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts
+++ b/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts
@@ -4,7 +4,6 @@ import { Datasource } from "entities/Datasource";
import _ from "lodash";
const initialState: DatasourcePaneReduxState = {
- selectedPlugin: "",
drafts: {},
actionRouteInfo: {},
expandDatasourceId: "",
@@ -13,7 +12,6 @@ const initialState: DatasourcePaneReduxState = {
};
export interface DatasourcePaneReduxState {
- selectedPlugin: string;
drafts: Record;
expandDatasourceId: string;
actionRouteInfo: Partial<{
@@ -27,14 +25,6 @@ export interface DatasourcePaneReduxState {
}
const datasourcePaneReducer = createReducer(initialState, {
- [ReduxActionTypes.SELECT_PLUGIN]: (
- state: DatasourcePaneReduxState,
- action: ReduxAction<{ pluginId: string }>,
- ) => ({
- ...state,
- selectedPlugin: action.payload.pluginId,
- }),
-
[ReduxActionTypes.UPDATE_DATASOURCE_DRAFT]: (
state: DatasourcePaneReduxState,
action: ReduxAction<{ id: string; draft: Partial }>,
diff --git a/app/client/src/sagas/ActionExecutionSagas.ts b/app/client/src/sagas/ActionExecutionSagas.ts
index f63eef993d..5d2110bb9e 100644
--- a/app/client/src/sagas/ActionExecutionSagas.ts
+++ b/app/client/src/sagas/ActionExecutionSagas.ts
@@ -33,7 +33,7 @@ import {
getPageList,
} from "selectors/editorSelectors";
import _ from "lodash";
-import AnalyticsUtil from "utils/AnalyticsUtil";
+import AnalyticsUtil, { EventName } from "utils/AnalyticsUtil";
import history from "utils/history";
import {
BUILDER_PAGE_URL,
@@ -46,7 +46,7 @@ import {
showRunActionConfirmModal,
updateAction,
} from "actions/actionActions";
-import { Action } from "entities/Action";
+import { Action, PluginType } from "entities/Action";
import ActionAPI, {
ActionExecutionResponse,
ActionResponse,
@@ -65,7 +65,6 @@ import { AppState } from "reducers";
import { mapToPropList } from "utils/AppsmithUtils";
import { validateResponse } from "sagas/ErrorSagas";
import { TypeOptions } from "react-toastify";
-import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants";
import {
updateAppPersistentStore,
@@ -687,8 +686,13 @@ function* runActionSaga(
const payload = createActionExecutionResponse(response);
const pageName = yield select(getCurrentPageNameByActionId, actionId);
- const eventName =
- actionObject.pluginType === PLUGIN_TYPE_API ? "RUN_API" : "RUN_QUERY";
+ let eventName: EventName = "RUN_API";
+ if (actionObject.pluginType === PluginType.DB) {
+ eventName = "RUN_QUERY";
+ }
+ if (actionObject.pluginType === PluginType.SAAS) {
+ eventName = "RUN_SAAS_API";
+ }
AnalyticsUtil.logEvent(eventName, {
actionId,
diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts
index 1a854827df..b494494017 100644
--- a/app/client/src/sagas/ActionSagas.ts
+++ b/app/client/src/sagas/ActionSagas.ts
@@ -42,20 +42,19 @@ import { transformRestAction } from "transformers/RestActionTransformer";
import {
getCurrentApplicationId,
getCurrentPageId,
- getDataSources,
} from "selectors/editorSelectors";
import AnalyticsUtil from "utils/AnalyticsUtil";
-import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
-import { Action, ActionViewMode } from "entities/Action";
+import { Action, ActionViewMode, PluginType } from "entities/Action";
import { ActionData } from "reducers/entityReducers/actionsReducer";
import {
getAction,
getCurrentPageNameByActionId,
getEditorConfig,
getPageNameByPageId,
+ getPlugin,
getSettingConfig,
+ getDatasources,
} from "selectors/entitiesSelector";
-import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
import history from "utils/history";
import {
API_EDITOR_ID_URL,
@@ -80,6 +79,7 @@ import {
} from "constants/messages";
import _, { merge } from "lodash";
import { getConfigInitialValues } from "components/formControls/utils";
+import { SAAS_EDITOR_API_ID_URL } from "pages/Editor/SaaSEditor/constants";
export function* createActionSaga(
actionPayload: ReduxAction<
@@ -261,7 +261,7 @@ export function* updateActionSaga(actionPayload: ReduxAction<{ id: string }>) {
);
let action = yield select(getAction, actionPayload.payload.id);
if (!action) throw new Error("Could not find action to update");
- const isApi = action.pluginType === "API";
+ const isApi = action.pluginType === PluginType.API;
if (isApi) {
action = transformRestAction(action);
@@ -277,17 +277,23 @@ export function* updateActionSaga(actionPayload: ReduxAction<{ id: string }>) {
response.data.id,
);
- if (action.pluginType === QUERY_CONSTANT) {
+ if (action.pluginType === PluginType.DB) {
AnalyticsUtil.logEvent("SAVE_QUERY", {
queryName: action.name,
pageName,
});
- } else if (action.pluginType === PLUGIN_TYPE_API) {
+ } else if (action.pluginType === PluginType.API) {
AnalyticsUtil.logEvent("SAVE_API", {
apiId: response.data.id,
apiName: response.data.name,
pageName: pageName,
});
+ } else if (action.pluginType === PluginType.SAAS) {
+ AnalyticsUtil.logEvent("SAVE_SAAS", {
+ apiId: response.data.id,
+ apiName: response.data.name,
+ pageName: pageName,
+ });
}
PerformanceTracker.stopAsyncTracking(
@@ -316,8 +322,9 @@ export function* deleteActionSaga(
const name = actionPayload.payload.name;
const action = yield select(getAction, id);
- const isApi = action.pluginType === PLUGIN_TYPE_API;
- const isQuery = action.pluginType === QUERY_CONSTANT;
+ const isApi = action.pluginType === PluginType.API;
+ const isQuery = action.pluginType === PluginType.DB;
+ const isSaas = action.pluginType === PluginType.SAAS;
const response: GenericApiResponse = yield ActionAPI.deleteAction(
id,
@@ -336,6 +343,14 @@ export function* deleteActionSaga(
apiID: id,
});
}
+ if (isSaas) {
+ const pageName = yield select(getCurrentPageNameByActionId, id);
+ AnalyticsUtil.logEvent("DELETE_SAAS", {
+ apiName: action.name,
+ pageName,
+ apiID: id,
+ });
+ }
if (isQuery) {
AnalyticsUtil.logEvent("DELETE_QUERY", {
queryName: action.name,
@@ -343,9 +358,10 @@ export function* deleteActionSaga(
}
const applicationId = yield select(getCurrentApplicationId);
const pageId = yield select(getCurrentPageId);
- if (isApi) {
+ if (isApi || isSaas) {
history.push(API_EDITOR_URL(applicationId, pageId));
}
+
if (isQuery) {
history.push(QUERIES_EDITOR_URL(applicationId, pageId));
}
@@ -425,7 +441,7 @@ function* copyActionSaga(
}) as Partial;
delete copyAction.id;
const response = yield ActionAPI.createAction(copyAction);
- const datasources = yield select(getDataSources);
+ const datasources = yield select(getDatasources);
const isValidResponse = yield validateResponse(response);
const pageName = yield select(getPageNameByPageId, response.data.pageId);
@@ -637,8 +653,9 @@ function* toggleActionExecuteOnLoadSaga(
function* handleMoveOrCopySaga(actionPayload: ReduxAction<{ id: string }>) {
const { id } = actionPayload.payload;
const action: Action = yield select(getAction, id);
- const isApi = action.pluginType === PLUGIN_TYPE_API;
- const isQuery = action.pluginType === QUERY_CONSTANT;
+ const isApi = action.pluginType === PluginType.API;
+ const isQuery = action.pluginType === PluginType.DB;
+ const isSaas = action.pluginType === PluginType.DB;
const applicationId = yield select(getCurrentApplicationId);
if (isApi) {
@@ -649,6 +666,17 @@ function* handleMoveOrCopySaga(actionPayload: ReduxAction<{ id: string }>) {
QUERIES_EDITOR_ID_URL(applicationId, action.pageId, action.id),
);
}
+ if (isSaas) {
+ const plugin = yield select(getPlugin, action.pluginId);
+ history.push(
+ SAAS_EDITOR_API_ID_URL(
+ applicationId,
+ action.pageId,
+ plugin.packageName,
+ action.id,
+ ),
+ );
+ }
}
export function* watchActionSagas() {
diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts
index 4ebdc80958..c5e07540cc 100644
--- a/app/client/src/sagas/ApiPaneSagas.ts
+++ b/app/client/src/sagas/ApiPaneSagas.ts
@@ -28,17 +28,27 @@ import {
QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID,
DATA_SOURCES_EDITOR_URL,
API_EDITOR_URL_WITH_SELECTED_PAGE_ID,
+ DATA_SOURCES_EDITOR_ID_URL,
} from "constants/routes";
import {
getCurrentApplicationId,
getCurrentPageId,
- getDataSources,
} from "selectors/editorSelectors";
import { initialize, autofill, change } from "redux-form";
import { Property } from "api/ActionAPI";
-import { createNewApiName, getNextEntityName } from "utils/AppsmithUtils";
+import {
+ createNewApiName,
+ getNextEntityName,
+ getQueryParams,
+} from "utils/AppsmithUtils";
import { getPluginIdOfPackageName } from "sagas/selectors";
-import { getAction, getActions, getPlugins } from "selectors/entitiesSelector";
+import {
+ getAction,
+ getActions,
+ getPlugins,
+ getDatasources,
+ getPlugin,
+} from "selectors/entitiesSelector";
import { ActionData } from "reducers/entityReducers/actionsReducer";
import { createActionRequest, setActionProperty } from "actions/actionActions";
import { Datasource } from "entities/Datasource";
@@ -381,7 +391,7 @@ function* handleActionCreatedSaga(actionPayload: ReduxAction) {
const action = yield select(getAction, id);
const data = { ...action };
- if (pluginType === "API") {
+ if (pluginType === PluginType.API) {
yield put(initialize(API_EDITOR_FORM_NAME, omit(data, "name")));
const applicationId = yield select(getCurrentApplicationId);
const pageId = yield select(getCurrentPageId);
@@ -393,6 +403,19 @@ function* handleActionCreatedSaga(actionPayload: ReduxAction) {
}
}
+function* handleDatasourceCreatedSaga(actionPayload: ReduxAction