diff --git a/.all-contributorsrc b/.all-contributorsrc
index d7b219ee1726..612cd3d7ca57 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -352,6 +352,26 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "somangshu",
+ "name": "Somangshu Goswami",
+ "avatar_url": "https://avatars.githubusercontent.com/u/11089579?v=4",
+ "profile": "http://appsmith.com",
+ "contributions": [
+ "bug",
+ "code"
+ ]
+ },
+ {
+ "login": "akshayrangasaid",
+ "name": "akshayrangasaid",
+ "avatar_url": "https://avatars.githubusercontent.com/u/76783810?v=4",
+ "profile": "https://github.com/akshayrangasaid",
+ "contributions": [
+ "content",
+ "ideas"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/.github/workflows/client-test.yml b/.github/workflows/client-test.yml
index 16d631b232bf..a42688f1fac0 100644
--- a/.github/workflows/client-test.yml
+++ b/.github/workflows/client-test.yml
@@ -371,6 +371,8 @@ jobs:
CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
CYPRESS_TESTUSERNAME2: ${{ secrets.CYPRESS_TESTUSERNAME2 }}
CYPRESS_TESTPASSWORD2: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
+ CYPRESS_S3_ACCESS_KEY: ${{ secrets.CYPRESS_S3_ACCESS_KEY }}
+ CYPRESS_S3_SECRET_KEY: ${{ secrets.CYPRESS_S3_SECRET_KEY }}
APPSMITH_DISABLE_TELEMETRY: true
APPSMITH_GOOGLE_MAPS_API_KEY: ${{ secrets.APPSMITH_GOOGLE_MAPS_API_KEY }}
POSTGRES_PASSWORD: postgres
diff --git a/.github/workflows/external-client-test.yml b/.github/workflows/external-client-test.yml
index aff3f8086289..61cd6176bed3 100644
--- a/.github/workflows/external-client-test.yml
+++ b/.github/workflows/external-client-test.yml
@@ -354,6 +354,8 @@ jobs:
CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
CYPRESS_TESTUSERNAME2: ${{ secrets.CYPRESS_TESTUSERNAME2 }}
CYPRESS_TESTPASSWORD2: ${{ secrets.CYPRESS_TESTPASSWORD1 }}
+ CYPRESS_S3_ACCESS_KEY: ${{ secrets.CYPRESS_S3_ACCESS_KEY }}
+ CYPRESS_S3_SECRET_KEY: ${{ secrets.CYPRESS_S3_SECRET_KEY }}
APPSMITH_DISABLE_TELEMETRY: true
APPSMITH_GOOGLE_MAPS_API_KEY: ${{ secrets.APPSMITH_GOOGLE_MAPS_API_KEY }}
POSTGRES_PASSWORD: postgres
diff --git a/.gitignore b/.gitignore
index 2d1d9bda93f0..f312b4ea14eb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
# test coverage
coverage-summary.json
+app/client/cypress/locators/Widgets.json
diff --git a/README.md b/README.md
index e811989f7400..add85fb058c4 100644
--- a/README.md
+++ b/README.md
@@ -188,6 +188,8 @@ We love our contributors! We're committed to fostering an open and welcoming env
 Navdeep Singh 💻 |
 Aswath K 💻 |
+  Somangshu Goswami 🐛 💻 |
+  akshayrangasaid 🖋 🤔 |
diff --git a/app/client/Dockerfile-cypress-test b/app/client/Dockerfile-cypress-test
deleted file mode 100644
index be82fbaceea1..000000000000
--- a/app/client/Dockerfile-cypress-test
+++ /dev/null
@@ -1,8 +0,0 @@
-# FROM cypress/browsers:node10.16.3-chrome80-ff73
-FROM nginx:1.17.9-alpine
-
-RUN apt update -y -q && \
- apt-get install -y -q nginx gettext-base && \
- apt-get clean && \
- rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
- yarn global add serve
diff --git a/app/client/cypress/fixtures/application-file.json b/app/client/cypress/fixtures/application-file.json
deleted file mode 100644
index d3a8c5d5ac2e..000000000000
--- a/app/client/cypress/fixtures/application-file.json
+++ /dev/null
@@ -1,175 +0,0 @@
-{
- "appsmithVersion":"1.5.14",
- "exportedApplication": {
- "userPermissions": [
- "canComment:applications",
- "manage:applications",
- "read:applications",
- "publish:applications",
- "makePublic:applications"
- ],
- "name": "testing app - pk",
- "isPublic": false,
- "appIsExample": false,
- "color": "#FE9F44",
- "icon": "heart",
- "new": true
- },
- "datasourceList": [],
- "pageList": [
- {
- "userPermissions": [
- "read:pages",
- "manage:pages"
- ],
- "unpublishedPage": {
- "name": "Page1",
- "layouts": [
- {
- "id": "60a77186cdbfc9440388285c",
- "userPermissions": [],
- "dsl": {
- "widgetName": "MainContainer",
- "backgroundColor": "none",
- "rightColumn": 1118,
- "snapColumns": 16,
- "detachFromLayout": true,
- "widgetId": "0",
- "topRow": 0,
- "bottomRow": 1280,
- "containerStyle": "none",
- "snapRows": 33,
- "parentRowSpace": 1,
- "type": "CANVAS_WIDGET",
- "canExtend": true,
- "version": 18,
- "minHeight": 1292,
- "parentColumnSpace": 1,
- "dynamicTriggerPathList": [],
- "dynamicBindingPathList": [],
- "leftColumn": 0,
- "children": [
- {
- "widgetName": "Button1",
- "rightColumn": 4,
- "isDefaultClickDisabled": true,
- "widgetId": "g7jf8v3wkq",
- "buttonStyle": "PRIMARY_BUTTON",
- "topRow": 0,
- "bottomRow": 1,
- "parentRowSpace": 40,
- "isVisible": true,
- "type": "BUTTON_WIDGET",
- "version": 1,
- "parentId": "0",
- "isLoading": false,
- "parentColumnSpace": 67.375,
- "leftColumn": 2,
- "text": "Submit",
- "isDisabled": false
- },
- {
- "widgetName": "Chart1",
- "rightColumn": 8,
- "allowHorizontalScroll": false,
- "widgetId": "ow55pc4z0z",
- "topRow": 5,
- "bottomRow": 13,
- "parentRowSpace": 40,
- "isVisible": true,
- "type": "CHART_WIDGET",
- "version": 1,
- "parentId": "0",
- "isLoading": false,
- "chartData": {
- "pftw37090s": {
- "seriesName": "Sales",
- "data": [
- {
- "x": "Mon",
- "y": 10000
- },
- {
- "x": "Tue",
- "y": 12000
- },
- {
- "x": "Wed",
- "y": 32000
- },
- {
- "x": "Thu",
- "y": 28000
- },
- {
- "x": "Fri",
- "y": 14000
- },
- {
- "x": "Sat",
- "y": 19000
- },
- {
- "x": "Sun",
- "y": 36000
- }
- ]
- }
- },
- "yAxisName": "Total Order Revenue $",
- "parentColumnSpace": 67.375,
- "chartName": "Last week's revenue",
- "leftColumn": 2,
- "xAxisName": "Last Week",
- "chartType": "LINE_CHART"
- }
- ]
- },
- "layoutOnLoadActions": [],
- "new": false
- }
- ],
- "userPermissions": []
- },
- "publishedPage": {
- "name": "Page1",
- "layouts": [
- {
- "id": "60a77186cdbfc9440388285c",
- "userPermissions": [],
- "dsl": {
- "widgetName": "MainContainer",
- "backgroundColor": "none",
- "rightColumn": 1224,
- "snapColumns": 16,
- "detachFromLayout": true,
- "widgetId": "0",
- "topRow": 0,
- "bottomRow": 1254,
- "containerStyle": "none",
- "snapRows": 33,
- "parentRowSpace": 1,
- "type": "CANVAS_WIDGET",
- "canExtend": true,
- "version": 4,
- "minHeight": 1292,
- "parentColumnSpace": 1,
- "dynamicBindingPathList": [],
- "leftColumn": 0,
- "children": []
- },
- "new": false
- }
- ],
- "userPermissions": []
- },
- "new": true
- }
- ],
- "publishedDefaultPageName": "Page1",
- "unpublishedDefaultPageName": "Page1",
- "actionList": [],
- "decryptedFields": {},
- "publishedLayoutmongoEscapedWidgets": {},
- "unpublishedLayoutmongoEscapedWidgets": {}
-}
diff --git a/app/client/cypress/fixtures/datasources.json b/app/client/cypress/fixtures/datasources.json
index 419c7106286c..a8597fa6c414 100644
--- a/app/client/cypress/fixtures/datasources.json
+++ b/app/client/cypress/fixtures/datasources.json
@@ -10,6 +10,11 @@
"postgres-databaseName": "fakeapi",
"postgres-username": "postgres",
"postgres-password": "postgres",
+ "mysql-host": "localhost",
+ "mysql-port": 3306,
+ "mysql-databaseName": "fakeapi",
+ "mysql-username": "root",
+ "mysql-password": "root123",
"restapi-url": "https://my-json-server.typicode.com/typicode/demo/posts",
"mongo-defaultDatabaseName": "sample_airbnb",
"connection-type": "Replica set",
diff --git a/app/client/cypress/fixtures/debuggerTableDsl.json b/app/client/cypress/fixtures/debuggerTableDsl.json
index afaed3b2cf9c..f2d81d08bbe4 100644
--- a/app/client/cypress/fixtures/debuggerTableDsl.json
+++ b/app/client/cypress/fixtures/debuggerTableDsl.json
@@ -36,6 +36,24 @@
"parentId": "0",
"widgetId": "7miqot30xy",
"dynamicBindingPathList": []
+ },
+ {
+ "isVisible": true,
+ "text": "{{test}}",
+ "buttonStyle": "PRIMARY_BUTTON",
+ "widgetName": "Button1",
+ "isDisabled": false,
+ "isDefaultClickDisabled": true,
+ "type": "BUTTON_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 74,
+ "parentRowSpace": 40,
+ "leftColumn": 5,
+ "rightColumn": 7,
+ "topRow": 2,
+ "bottomRow": 3,
+ "parentId": "0",
+ "widgetId": "3qg87le9t4"
}
]
}
diff --git a/app/client/cypress/fixtures/formWithInputdsl.json b/app/client/cypress/fixtures/formWithInputdsl.json
new file mode 100644
index 000000000000..5a33e795b1fb
--- /dev/null
+++ b/app/client/cypress/fixtures/formWithInputdsl.json
@@ -0,0 +1,183 @@
+{
+ "dsl": {
+ "widgetName": "MainContainer",
+ "backgroundColor": "none",
+ "rightColumn": 966,
+ "snapColumns": 64,
+ "detachFromLayout": true,
+ "widgetId": "0",
+ "topRow": 0,
+ "bottomRow": 1290,
+ "containerStyle": "none",
+ "snapRows": 128,
+ "parentRowSpace": 1,
+ "type": "CANVAS_WIDGET",
+ "canExtend": true,
+ "version": 30,
+ "minHeight": 1292,
+ "parentColumnSpace": 1,
+ "dynamicBindingPathList": [],
+ "leftColumn": 0,
+ "children": [
+ {
+ "widgetName": "Form1",
+ "backgroundColor": "white",
+ "rightColumn": 40,
+ "widgetId": "j77dthxf61",
+ "topRow": 8,
+ "bottomRow": 60,
+ "parentRowSpace": 40,
+ "isVisible": true,
+ "type": "FORM_WIDGET",
+ "version": 1,
+ "parentId": "0",
+ "isLoading": false,
+ "parentColumnSpace": 74,
+ "leftColumn": 12,
+ "dynamicBindingPathList": [],
+ "children": [
+ {
+ "widgetName": "Canvas1",
+ "rightColumn": 2072,
+ "detachFromLayout": true,
+ "widgetId": "sidaue1kdu",
+ "containerStyle": "none",
+ "topRow": 0,
+ "bottomRow": 2080,
+ "parentRowSpace": 1,
+ "isVisible": true,
+ "canExtend": false,
+ "type": "CANVAS_WIDGET",
+ "version": 1,
+ "parentId": "j77dthxf61",
+ "minHeight": 520,
+ "isLoading": false,
+ "parentColumnSpace": 1,
+ "leftColumn": 0,
+ "dynamicBindingPathList": [],
+ "children": [
+ {
+ "widgetName": "Text1",
+ "rightColumn": 48,
+ "textAlign": "LEFT",
+ "widgetId": "ac6cc8wmlu",
+ "topRow": 0,
+ "bottomRow": 4,
+ "isVisible": true,
+ "type": "TEXT_WIDGET",
+ "fontStyle": "BOLD",
+ "version": 1,
+ "textColor": "#231F20",
+ "parentId": "sidaue1kdu",
+ "isLoading": false,
+ "leftColumn": 0,
+ "dynamicBindingPathList": [],
+ "fontSize": "HEADING1",
+ "text": "Form"
+ },
+ {
+ "isRequired": false,
+ "widgetName": "Checkbox1",
+ "rightColumn": 52,
+ "widgetId": "szjhneuog5",
+ "topRow": 16,
+ "bottomRow": 20,
+ "parentRowSpace": 38,
+ "isVisible": true,
+ "label": "Label",
+ "type": "CHECKBOX_WIDGET",
+ "version": 1,
+ "parentId": "e3tq9qwta6",
+ "isLoading": false,
+ "parentColumnSpace": 71.75,
+ "leftColumn": 40,
+ "dynamicBindingPathList": [],
+ "defaultCheckedState": true,
+ "isDisabled": false
+ },
+ {
+ "widgetName": "Switch1",
+ "rightColumn": 52,
+ "widgetId": "szjhneuog9",
+ "topRow": 72,
+ "bottomRow": 76,
+ "parentRowSpace": 38,
+ "isVisible": true,
+ "label": "Switch",
+ "type": "SWITCH_WIDGET",
+ "defaultSwitchState": true,
+ "version": 1,
+ "parentId": "e3tq9qwta6",
+ "isLoading": false,
+ "parentColumnSpace": 71.75,
+ "leftColumn": 40,
+ "dynamicBindingPathList": [],
+ "isDisabled": false
+ },
+ {
+ "resetFormOnClick": true,
+ "widgetName": "FormButton1",
+ "rightColumn": 64,
+ "isDefaultClickDisabled": true,
+ "widgetId": "xyn7t20lhv",
+ "buttonStyle": "PRIMARY_BUTTON",
+ "topRow": 44,
+ "bottomRow": 48,
+ "isVisible": true,
+ "type": "FORM_BUTTON_WIDGET",
+ "version": 1,
+ "parentId": "sidaue1kdu",
+ "isLoading": false,
+ "disabledWhenInvalid": true,
+ "leftColumn": 48,
+ "dynamicBindingPathList": [],
+ "text": "Submit"
+ },
+ {
+ "resetFormOnClick": true,
+ "widgetName": "FormButton2",
+ "rightColumn": 48,
+ "isDefaultClickDisabled": true,
+ "widgetId": "xlrmeiioaa",
+ "buttonStyle": "SECONDARY_BUTTON",
+ "topRow": 44,
+ "bottomRow": 48,
+ "isVisible": true,
+ "type": "FORM_BUTTON_WIDGET",
+ "version": 1,
+ "parentId": "sidaue1kdu",
+ "isLoading": false,
+ "disabledWhenInvalid": false,
+ "leftColumn": 32,
+ "dynamicBindingPathList": [],
+ "text": "Reset"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "isVisible": true,
+ "inputType": "TEXT",
+ "label": "",
+ "widgetName": "Input1",
+ "version": 1,
+ "resetOnSubmit": true,
+ "isRequired": false,
+ "isDisabled": false,
+ "allowCurrencyChange": false,
+ "type": "INPUT_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 14.90625,
+ "parentRowSpace": 10,
+ "leftColumn": 42,
+ "rightColumn": 62,
+ "topRow": 13,
+ "bottomRow": 17,
+ "parentId": "0",
+ "widgetId": "fxixmi3q4s"
+ }
+ ],
+ "dynamicTriggerPathList": []
+ }
+}
\ No newline at end of file
diff --git a/app/client/cypress/fixtures/saveAction.json b/app/client/cypress/fixtures/saveAction.json
index e247df9ce532..e556089c53ad 100644
--- a/app/client/cypress/fixtures/saveAction.json
+++ b/app/client/cypress/fixtures/saveAction.json
@@ -25,7 +25,6 @@
}
],
"authentication": {
- "authenticationType": "dbAuth",
"authenticationType": "dbAuth",
"authType": "SCRAM_SHA_1",
"username": "cypress-test",
diff --git a/app/client/cypress/fixtures/tableNewDsl.json b/app/client/cypress/fixtures/tableNewDsl.json
index 30fda1987fca..d64d333c6912 100644
--- a/app/client/cypress/fixtures/tableNewDsl.json
+++ b/app/client/cypress/fixtures/tableNewDsl.json
@@ -1,129 +1,127 @@
{
- "dsl": {
- "widgetName": "MainContainer",
- "backgroundColor": "none",
- "rightColumn": 1224,
- "snapColumns": 16,
- "detachFromLayout": true,
- "widgetId": "0",
- "topRow": 0,
- "bottomRow": 1280,
- "containerStyle": "none",
- "snapRows": 33,
- "parentRowSpace": 1,
- "type": "CANVAS_WIDGET",
- "canExtend": true,
- "version": 8,
- "minHeight": 1292,
- "parentColumnSpace": 1,
- "dynamicBindingPathList": [],
- "leftColumn": 0,
- "children": [
- {
- "isVisible": true,
- "label": "Data",
- "widgetName": "Table1",
- "searchKey": "",
- "tableData": "{{\n[\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n }\n]\n}}",
- "type": "TABLE_WIDGET",
- "isLoading": false,
- "parentColumnSpace": 74,
- "parentRowSpace": 40,
- "leftColumn": 2,
- "rightColumn": 10,
- "topRow": 12,
- "bottomRow": 19,
- "parentId": "0",
- "widgetId": "sb070qr2ir",
+ "dsl": {
+ "widgetName": "MainContainer",
+ "backgroundColor": "none",
+ "rightColumn": 1224,
+ "snapColumns": 16,
+ "detachFromLayout": true,
+ "widgetId": "0",
+ "topRow": 0,
+ "bottomRow": 1280,
+ "containerStyle": "none",
+ "snapRows": 33,
+ "parentRowSpace": 1,
+ "type": "CANVAS_WIDGET",
+ "canExtend": true,
+ "version": 8,
+ "minHeight": 1292,
+ "parentColumnSpace": 1,
"dynamicBindingPathList": [],
- "primaryColumns": {
- "id": {
- "index": 0,
- "width": 150,
- "id": "id",
- "horizontalAlignment": "LEFT",
- "verticalAlignment": "CENTER",
- "columnType": "text",
- "textColor": "#4E5D78",
- "textSize": "PARAGRAPH",
- "fontStyle": "NORMAL",
- "enableFilter": true,
- "enableSort": true,
+ "leftColumn": 0,
+ "children": [{
"isVisible": true,
- "isDerived": false,
- "label": "id",
- "computedValue": ""
- },
- "email": {
- "index": 1,
- "width": 150,
- "id": "email",
- "horizontalAlignment": "LEFT",
- "verticalAlignment": "CENTER",
- "columnType": "text",
- "textColor": "#4E5D78",
- "textSize": "PARAGRAPH",
- "fontStyle": "NORMAL",
- "enableFilter": true,
- "enableSort": true,
- "isVisible": true,
- "isDerived": false,
- "label": "email",
- "computedValue": ""
- },
- "userName": {
- "index": 2,
- "width": 150,
- "id": "userName",
- "horizontalAlignment": "LEFT",
- "verticalAlignment": "CENTER",
- "columnType": "text",
- "textColor": "#4E5D78",
- "textSize": "PARAGRAPH",
- "fontStyle": "NORMAL",
- "enableFilter": true,
- "enableSort": true,
- "isVisible": true,
- "isDerived": false,
- "label": "userName",
- "computedValue": ""
- },
- "productName": {
- "index": 3,
- "width": 150,
- "id": "productName",
- "horizontalAlignment": "LEFT",
- "verticalAlignment": "CENTER",
- "columnType": "text",
- "textColor": "#4E5D78",
- "textSize": "PARAGRAPH",
- "fontStyle": "NORMAL",
- "enableFilter": true,
- "enableSort": true,
- "isVisible": true,
- "isDerived": false,
- "label": "productName",
- "computedValue": ""
- },
- "orderAmount": {
- "index": 4,
- "width": 150,
- "id": "orderAmount",
- "horizontalAlignment": "LEFT",
- "verticalAlignment": "CENTER",
- "columnType": "text",
- "textColor": "#4E5D78",
- "textSize": "PARAGRAPH",
- "fontStyle": "NORMAL",
- "enableFilter": true,
- "enableSort": true,
- "isVisible": true,
- "isDerived": false,
- "label": "orderAmount",
- "computedValue": ""
- }
- }
- }
- ]
- }
-}
+ "label": "Data",
+ "widgetName": "Table1",
+ "searchKey": "",
+ "tableData": "{{\n[\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n }\n]\n}}",
+ "type": "TABLE_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 74,
+ "parentRowSpace": 40,
+ "leftColumn": 2,
+ "rightColumn": 10,
+ "topRow": 12,
+ "bottomRow": 19,
+ "parentId": "0",
+ "widgetId": "sb070qr2ir",
+ "dynamicBindingPathList": [],
+ "primaryColumns": {
+ "id": {
+ "index": 0,
+ "width": 150,
+ "id": "id",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "id",
+ "computedValue": ""
+ },
+ "email": {
+ "index": 1,
+ "width": 150,
+ "id": "email",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "email",
+ "computedValue": ""
+ },
+ "userName": {
+ "index": 2,
+ "width": 150,
+ "id": "userName",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "userName",
+ "computedValue": ""
+ },
+ "productName": {
+ "index": 3,
+ "width": 150,
+ "id": "productName",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "productName",
+ "computedValue": ""
+ },
+ "orderAmount": {
+ "index": 4,
+ "width": 150,
+ "id": "orderAmount",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "orderAmount",
+ "computedValue": ""
+ }
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/app/client/cypress/fixtures/tableNewDslWithPagination.json b/app/client/cypress/fixtures/tableNewDslWithPagination.json
new file mode 100644
index 000000000000..08002f6b5d13
--- /dev/null
+++ b/app/client/cypress/fixtures/tableNewDslWithPagination.json
@@ -0,0 +1,127 @@
+{
+ "dsl": {
+ "widgetName": "MainContainer",
+ "backgroundColor": "none",
+ "rightColumn": 1224,
+ "snapColumns": 16,
+ "detachFromLayout": true,
+ "widgetId": "0",
+ "topRow": 0,
+ "bottomRow": 1280,
+ "containerStyle": "none",
+ "snapRows": 33,
+ "parentRowSpace": 1,
+ "type": "CANVAS_WIDGET",
+ "canExtend": true,
+ "version": 8,
+ "minHeight": 1292,
+ "parentColumnSpace": 1,
+ "dynamicBindingPathList": [],
+ "leftColumn": 0,
+ "children": [{
+ "isVisible": true,
+ "label": "Data",
+ "widgetName": "Table1",
+ "searchKey": "",
+ "tableData": "{{[\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n },\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n }\n]}}",
+ "type": "TABLE_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 74,
+ "parentRowSpace": 40,
+ "leftColumn": 2,
+ "rightColumn": 10,
+ "topRow": 12,
+ "bottomRow": 19,
+ "parentId": "0",
+ "widgetId": "sb070qr2ir",
+ "dynamicBindingPathList": [],
+ "primaryColumns": {
+ "id": {
+ "index": 0,
+ "width": 150,
+ "id": "id",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "id",
+ "computedValue": ""
+ },
+ "email": {
+ "index": 1,
+ "width": 150,
+ "id": "email",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "email",
+ "computedValue": ""
+ },
+ "userName": {
+ "index": 2,
+ "width": 150,
+ "id": "userName",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "userName",
+ "computedValue": ""
+ },
+ "productName": {
+ "index": 3,
+ "width": 150,
+ "id": "productName",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "productName",
+ "computedValue": ""
+ },
+ "orderAmount": {
+ "index": 4,
+ "width": 150,
+ "id": "orderAmount",
+ "horizontalAlignment": "LEFT",
+ "verticalAlignment": "CENTER",
+ "columnType": "text",
+ "textColor": "#4E5D78",
+ "textSize": "PARAGRAPH",
+ "fontStyle": "NORMAL",
+ "enableFilter": true,
+ "enableSort": true,
+ "isVisible": true,
+ "isDerived": false,
+ "label": "orderAmount",
+ "computedValue": ""
+ }
+ }
+ }]
+ }
+}
\ No newline at end of file
diff --git a/app/client/cypress/fixtures/testdata.json b/app/client/cypress/fixtures/testdata.json
index f449eeeb45d1..890e54176854 100644
--- a/app/client/cypress/fixtures/testdata.json
+++ b/app/client/cypress/fixtures/testdata.json
@@ -46,6 +46,7 @@
"apiMultipartBodyType": "multi-part",
"defaultMoustacheData": "{{Input1.text",
"defaultInputWidget": "{{Table1.selectedRow.id",
+ "sortedColumn": "{{Table1.sortOrder.column",
"deafultDropDownWidget": [
{
"label": "{{Table1.selectedRow.email}}",
diff --git a/app/client/cypress/init-mysql-dump-for-test.sql b/app/client/cypress/init-mysql-dump-for-test.sql
new file mode 100644
index 000000000000..f90f190d1710
--- /dev/null
+++ b/app/client/cypress/init-mysql-dump-for-test.sql
@@ -0,0 +1,40 @@
+CREATE DATABASE fakeapi;
+use fakeapi;
+
+CREATE TABLE configs (
+ id int NOT NULL AUTO_INCREMENT,
+ configName varchar(255) NOT NULL,
+ configJson JSON,
+ configVersion int ,
+ updatedAt TIMESTAMP,
+ updatedBy varchar(255),
+ primary key (id)
+);
+
+CREATE TABLE users (
+ id int NOT NULL AUTO_INCREMENT,
+ name varchar(255),
+ createdAt datetime,
+ updatedAt datetime,
+ status varchar(255),
+ gender varchar(255),
+ avatar varchar(255),
+ email varchar(255),
+ address varchar(255),
+ role varchar(255),
+ dob date,
+ phoneNo varchar(255),
+ primary key (id)
+);
+
+
+insert into configs (id, configName, configJson, configVersion, updatedAt, updatedBy)
+values (3, 'New Config', '{"key": "val1"}', 1, '2020-08-26 11:14:28', ''),
+(5, 'New Config', '{"key": "val2"}', 1, '2020-08-26 11:14:28', '');
+
+
+insert into users (id, name, createdAt, updatedAt, status, gender, avatar, email, address, role, dob, phoneNo) values
+(7, 'Test user 7', '2019-08-07 21:36:27', '2019-10-21 03:23:42', 'APPROVED', 'Male', 'https://robohash.org/quiofficiadicta.jpg?size=100x100&set=set1' ,'xkainz6@ihg.com', '19624 Scofield Way', 'Admin','1993-08-14', ''),
+(8, 'Test user 8', '2019-08-07 21:36:27', '2019-10-21 03:23:42', 'APPROVED', 'Male', 'https://robohash.org/quiofficiadicta.jpg?size=100x100&set=set1' ,'xkainz6@ihg.com', '19624 Scofield Way', 'Admin','1993-08-14', ''),
+(9, 'Test user 9', '2019-08-07 21:36:27', '2019-10-21 03:23:42', 'APPROVED', 'Male', 'https://robohash.org/quiofficiadicta.jpg?size=100x100&set=set1' ,'xkainz6@ihg.com', '19624 Scofield Way', 'Admin','1993-08-14', '');
+
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js
index 23fcf4beb0d2..9330f7eff2a3 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js
@@ -5,7 +5,6 @@ const commonlocators = require("../../../../locators/commonlocators.json");
describe("Export application as a JSON file", function() {
let orgid;
let appid;
- let currentUrl;
let newOrganizationName;
let appname;
@@ -28,6 +27,17 @@ describe("Export application as a JSON file", function() {
.click({ force: true });
cy.get(homePage.exportAppFromMenu).click({ force: true });
cy.get(homePage.toastMessage).should("contain", "Successfully exported");
+ // fetching the exported app file manually to be verified.
+ cy.get(`a[id=t--export-app-link]`).then((anchor) => {
+ const url = anchor.prop("href");
+ cy.request(url).then(({ headers }) => {
+ expect(headers).to.have.property("content-type", "application/json");
+ expect(headers).to.have.property(
+ "content-disposition",
+ `attachment; filename*=UTF-8''${appname}.json`,
+ );
+ });
+ });
cy.LogOut();
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_InputWidget_Table_Sorting_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_InputWidget_Table_Sorting_spec.js
index 5413f82f835f..27d16cbb8df6 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_InputWidget_Table_Sorting_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Binding/Bind_InputWidget_Table_Sorting_spec.js
@@ -50,4 +50,32 @@ describe("Binding the Table and input Widget", function() {
.should("contain", tabValue);
});
});
+
+ it("validation of column id displayed in input widgets based on sorted column", function() {
+ cy.SearchEntityandOpen("Input1");
+ cy.get(".t--property-control-defaulttext .CodeMirror textarea")
+ .first()
+ .focus()
+ .type("{ctrl}{shift}{downarrow}")
+ .then(($cm) => {
+ if ($cm.val() !== "") {
+ cy.get(".t--property-control-defaulttext .CodeMirror textarea")
+ .first()
+ .clear({
+ force: true,
+ });
+ }
+ });
+ cy.get(widgetsPage.defaultInput).type(testdata.sortedColumn);
+ cy.get(commonlocators.editPropCrossButton).click({ force: true });
+ cy.wait("@updateLayout").should(
+ "have.nested.property",
+ "response.body.responseMeta.status",
+ 200,
+ );
+ cy.get(publish.inputWidget + " " + "input")
+ .first()
+ .invoke("attr", "value")
+ .should("contain", "id");
+ });
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js
index d71d5f3fbbb2..9ae1020af501 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Logs_spec.js
@@ -11,6 +11,7 @@ describe("Debugger logs", function() {
cy.get("button")
.contains("Submit")
.click({ force: true });
+ cy.openPropertyPane("buttonwidget");
cy.testJsontext("label", "Test");
cy.get(".t--debugger").click();
cy.get(".t--debugger-log-state").contains("Test");
@@ -24,7 +25,15 @@ describe("Debugger logs", function() {
cy.get(commonlocators.homeIcon).click({ force: true });
cy.generateUUID().then((id) => {
cy.CreateAppInFirstListedOrg(id);
- cy.contains(debuggerLocators.debuggerIcon, 0);
+ cy.get(debuggerLocators.errorCount).should("not.exist");
});
});
+
+ it("Api headers need to be shown as headers in logs", function() {
+ // TODO
+ });
+
+ it("Api body needs to be shown as JSON when possible", function() {
+ // TODO
+ });
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js
index 2fd3a73d96a1..872fd5bf90c4 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/PageOnLoad_spec.js
@@ -18,6 +18,10 @@ describe("Check debugger logs state when there are onPageLoad actions", function
cy.get(explorer.addWidget).click();
cy.reload();
- cy.contains(debuggerLocators.debuggerIcon, 0);
+ // Wait for the debugger icon to be visible
+ cy.get(".t--debugger").should("be.visible");
+ cy.get(debuggerLocators.errorCount).should("not.exist");
+ cy.wait("@postExecute");
+ cy.contains(debuggerLocators.errorCount, 1);
});
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js
index b90c506c6f94..cb72156dfab2 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js
@@ -22,4 +22,12 @@ describe("Widget error state", function() {
cy.get(debuggerLocators.debuggerLogState).contains("Test");
});
+
+ it("All errors should be expanded by default", function() {
+ cy.testJsontext("label", "{{[]}}");
+
+ cy.get(".t--debugger-message")
+ .should("be.visible")
+ .should("have.length", 2);
+ });
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js
index 584c9ce45e97..1fbb9bb23fa8 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Chart_spec.js
@@ -348,7 +348,14 @@ describe("Chart Widget Functionality", function() {
cy.UpdateChartType("Pie Chart");
cy.get(widgetsPage.toggleChartType).click({ force: true });
cy.testJsontext("charttype", "CUSTOM_FUSION_CHART");
-
+ cy.wait("@updateLayout").should(
+ "have.nested.property",
+ "response.body.responseMeta.status",
+ 200,
+ );
+ cy.get(viewWidgetsPage.Chartlabel + ":first-child", {
+ timeout: 10000,
+ }).should("have.css", "opacity", "1");
//Verifying X-axis labels
cy.get(viewWidgetsPage.chartWidget).should("have.css", "opacity", "1");
const labels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"];
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Column_Resize_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Column_Resize_spec.js
index 7eb900087021..038d411031ce 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Column_Resize_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Column_Resize_spec.js
@@ -11,13 +11,14 @@ describe("Table Widget Functionality with Hidden and Resized Columns", function(
it("Table Widget Functionality with Hidden and Resized Columns", function() {
cy.PublishtheApp();
+ // Verify column header width should be equal to table width
cy.get(".t--widget-tablewidget")
.invoke("outerWidth")
.then((tableWidth) => {
cy.get(".t--widget-tablewidget .thead .tr")
.invoke("outerWidth")
- .then((width) => {
- expect(width).to.be.at.least(tableWidth);
+ .then((columnHeaderWidth) => {
+ expect(columnHeaderWidth).to.be.at.least(tableWidth);
});
});
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Derived_Column_Data_validation_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Derived_Column_Data_validation_spec.js
index d57c23fac35b..7e96f3888363 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Derived_Column_Data_validation_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Derived_Column_Data_validation_spec.js
@@ -14,42 +14,59 @@ describe("Test Create Api and Bind to Table widget", function() {
});
it("Create an API and Execute the API and bind with Table", function() {
+ // Create and execute an API and bind with table
cy.createAndFillApi(this.data.paginationUrl, this.data.paginationParam);
cy.RunAPI();
});
it("Validate Table with API data and then add a column", function() {
+ // Open property pane
cy.SearchEntityandOpen("Table1");
+ // Clear Table data and enter Apil data into table data
cy.testJsontext("tabledata", "{{Api1.data.users}}");
+ // Check Widget properties
cy.CheckWidgetProperties(commonlocators.serverSidePaginationCheckbox);
+ // Open Text1 in propert pane
cy.SearchEntityandOpen("Text1");
+ // Change the Text value to selected row url
cy.testJsontext("text", "{{Table1.selectedRow.url}}");
+ // Open Table1 propert pane
cy.SearchEntityandOpen("Table1");
+ // Compare table 1st index data with itself
cy.readTabledata("0", "0").then((tabData) => {
const tableData = tabData;
localStorage.setItem("tableDataPage1", tableData);
});
+ // Verify 1st index data
cy.ValidateTableData("1");
+ // add new column
cy.addColumn("CustomColumn");
});
it("Table widget toggle test for background color", function() {
+ // Open id property pane
cy.editColumn("id");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
+ // Click on cell background JS button
cy.get(widgetsPage.toggleJsBcgColor)
.first()
.click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
+ // Change the cell background color to green
cy.toggleJsAndUpdate("tabledata", "Green");
+ // Go back to table property pane
cy.get(".t--property-pane-back-btn").click({ force: true });
cy.wait("@updateLayout");
+ // verify the cell background color
cy.readTabledataValidateCSS("1", "0", "background-color", "rgb(0, 128, 0)");
});
it("Edit column name and validate test for computed value based on column type selected", function() {
+ // opoen customColumn1 property pane
cy.editColumn("customColumn1");
+ // Enter Apil 1st user email data into customColumn1
cy.readTabledataPublish("1", "9").then((tabData) => {
const tabValue = tabData;
cy.updateComputedValue("{{Api1.data.users[0].email}}");
@@ -62,19 +79,24 @@ describe("Test Create Api and Bind to Table widget", function() {
});
it("Update table json data and check the column names updated", function() {
+ // Open table propert pane
cy.SearchEntityandOpen("Table1");
+ // Change the table data
cy.testJsontext("tabledata", JSON.stringify(this.data.TableInputUpdate));
cy.wait("@updateLayout");
+ // verify columns are visible or not in the propert pane
cy.tableColumnDataValidation("id");
cy.tableColumnDataValidation("email");
cy.tableColumnDataValidation("userName");
cy.tableColumnDataValidation("productName");
cy.tableColumnDataValidation("orderAmount");
cy.tableColumnDataValidation("customColumn1");
+ // Hide the columns in property pane
cy.hideColumn("email");
cy.hideColumn("userName");
cy.hideColumn("productName");
cy.hideColumn("orderAmount");
+ // verify customColumn is visible in the table
cy.get(".draggable-header:contains('CustomColumn')").should("be.visible");
cy.closePropertyPane();
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_GeneralProperty_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_GeneralProperty_spec.js
index e66f9eedfbde..533a43d250b2 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_GeneralProperty_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_GeneralProperty_spec.js
@@ -13,161 +13,222 @@ describe("Table Widget property pane feature validation", function() {
});
it("Test to validate table pagination is disabled", function() {
+ // Verify pagination is disabled
cy.get(".t--table-widget-prev-page").should("have.attr", "disabled");
cy.get(".t--table-widget-next-page").should("have.attr", "disabled");
cy.get(".t--table-widget-page-input input").should("have.attr", "disabled");
});
it("Test to validate text allignment", function() {
+ // Open property pane
cy.openPropertyPane("tablewidget");
+ // Change the text align to center
cy.get(widgetsPage.centerAlign)
.first()
.click({ force: true });
+ // Verify the center text alignment
cy.readTabledataValidateCSS("1", "0", "justify-content", "center");
+ // Change the text align to right
cy.get(widgetsPage.rightAlign)
.first()
.click({ force: true });
+ // Verify the right text alignment
cy.readTabledataValidateCSS("1", "0", "justify-content", "flex-end");
+ // Change the text align to left
cy.get(widgetsPage.leftAlign)
.first()
.click({ force: true });
+ // verify the left text alignment
cy.readTabledataValidateCSS("1", "0", "justify-content", "flex-start");
});
it("Test to validate column heading allignment", function() {
// cy.openPropertyPane("tablewidget");
+ // Change the text align to center
cy.get(widgetsPage.centerAlign)
.first()
.click({ force: true });
+ // Verify the column headings are center aligned
cy.get(".draggable-header")
.first()
.should("have.css", "text-align", "center");
+ // Change the text align to right
cy.get(widgetsPage.rightAlign)
.first()
.click({ force: true });
+ // Verify the column headings are right aligned
cy.get(".draggable-header")
.first()
.should("have.css", "text-align", "right");
+ // Change the text align to left
cy.get(widgetsPage.leftAlign)
.first()
.click({ force: true });
+ // Verify the column headings are left aligned
cy.get(".draggable-header")
.first()
.should("have.css", "text-align", "left");
});
it("Test to validate text format", function() {
+ // Select the bold font style
cy.get(widgetsPage.bold).click({ force: true });
+ // Varify the font style is bold
cy.readTabledataValidateCSS("1", "0", "font-weight", "700");
+ // Change the font style to italic
cy.get(widgetsPage.italics).click({ force: true });
+ // Verify the font style is italic
cy.readTabledataValidateCSS("1", "0", "font-style", "italic");
});
it("Test to validate vertical allignment", function() {
+ // Select the top vertical alignment
cy.get(widgetsPage.verticalTop).click({ force: true });
+ // verify vertical alignment is top
cy.readTabledataValidateCSS("1", "0", "align-items", "flex-start");
+ // Change the vertical alignment to center
cy.get(widgetsPage.verticalCenter)
.last()
.click({ force: true });
+ // Verify the vertical alignment is centered
cy.readTabledataValidateCSS("1", "0", "align-items", "center");
+ // Change the vertical alignment to bottom
cy.get(widgetsPage.verticalBottom)
.last()
.click({ force: true });
+ // Verify the vertical alignment is bottom
cy.readTabledataValidateCSS("1", "0", "align-items", "flex-end");
cy.get(commonlocators.editPropCrossButton).click({ force: true });
});
it("Table widget toggle test for text alignment", function() {
+ // Open property pane
cy.openPropertyPane("tablewidget");
+ // Open property pane of column "id"
cy.editColumn("id");
+ // Click on text align JS
cy.get(widgetsPage.toggleTextAlign)
.first()
.click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
+ // Change the text align value to right for michael and left for others
cy.toggleJsAndUpdate("tabledata", testdata.bindingGenAlign);
+ // Close propert pane
cy.get(commonlocators.editPropCrossButton).click({ force: true });
+ // Verify the text michael id is right aligned
cy.readTabledataValidateCSS("0", "0", "justify-content", "flex-end");
+ // Verify the 2nd id is left aligned
cy.readTabledataValidateCSS("1", "0", "justify-content", "flex-start");
});
it("Table widget change text size and validate", function() {
+ // Verify font size is 14px
cy.readTabledataValidateCSS("0", "0", "font-size", "14px");
+ // Open property pane
cy.openPropertyPane("tablewidget");
+ // Open property pane
cy.editColumn("id");
+ // Click on text size JS
cy.get(widgetsPage.toggleTextAlign)
.first()
.click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
+ // Open txe size dropdown options
cy.get(widgetsPage.textSize)
.last()
.click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
+ // Select Heading 1 text size
cy.selectTextSize("Heading 1");
+ // Verify the font size is 24px
cy.readTabledataValidateCSS("0", "0", "font-size", "24px");
+ // close propert pane
cy.get(commonlocators.editPropCrossButton).click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
+ // Verify the font size is 24px
cy.readTabledataValidateCSS("0", "0", "font-size", "24px");
});
it("Test to validate open new tab icon shows when URL type data is hovered", function() {
+ // Open property pane
cy.openPropertyPane("tablewidget");
+ // Open email property pane
cy.editColumn("email");
+ // Change column type to url
cy.changeColumnType("URL");
+ // Show the url hidden icon in front of first email
cy.get(
`.t--widget-tablewidget .tbody .td[data-rowindex=1][data-colindex=1] .hidden-icon`,
).invoke("show");
+ // Verify the url icon is visible on hover over email
cy.get(
`.t--widget-tablewidget .tbody .td[data-rowindex=1][data-colindex=1] .hidden-icon`,
).should("be.visible");
+ // Close property pane
cy.get(commonlocators.editPropCrossButton).click();
});
it("Edit column name and test for table header changes", function() {
+ // Open property pane
cy.openPropertyPane("tablewidget");
+ // Open email property pane
cy.editColumn("email");
+ // CHange the Column email name to Email Address
cy.editColName("Email Address");
+ // verify changed email name is visible
cy.get(".draggable-header:contains('Email Address')").should("be.visible");
cy.get(commonlocators.editPropCrossButton).click();
});
it("Test to validate text color and text background", function() {
+ // Open property pane
cy.openPropertyPane("tablewidget");
+ // Click on text color input field
cy.get(widgetsPage.textColor)
.first()
.click({ force: true });
+ // Select green color
cy.xpath(widgetsPage.greenColor).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.wait("@updateLayout");
+ // Verify the text color is green
cy.readTabledataValidateCSS("1", "0", "color", "rgb(3, 179, 101)");
+ // Change the text color and enter purple in input field
cy.get(widgetsPage.textColor)
.clear({ force: true })
.type("purple", { force: true });
cy.wait("@updateLayout");
+ // Verify the text color is purple
cy.readTabledataValidateCSS("1", "0", "color", "rgb(128, 0, 128)");
+ // Click on cell background color
cy.get(widgetsPage.backgroundColor)
.first()
.click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
+ // select the green color
cy.xpath(widgetsPage.greenColor)
.first()
.click();
cy.wait("@updateLayout");
+ // Verify the cell background color is green
cy.readTabledataValidateCSS(
"1",
"0",
"background",
"rgb(3, 179, 101) none repeat scroll 0% 0% / auto padding-box border-box",
);
+ // Change the cell background color and enter purple in input field
cy.get(widgetsPage.backgroundColor)
.clear({ force: true })
.type("purple", { force: true });
cy.wait("@updateLayout");
+ // Verify the cell background color is purple
cy.readTabledataValidateCSS(
"1",
"0",
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 42c7b7617300..af350cf09c14 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
@@ -1,8 +1,7 @@
const widgetsPage = require("../../../../locators/Widgets.json");
const commonlocators = require("../../../../locators/commonlocators.json");
const publish = require("../../../../locators/publishWidgetspage.json");
-const dsl = require("../../../../fixtures/tableNewDsl.json");
-const pages = require("../../../../locators/Pages.json");
+const dsl = require("../../../../fixtures/tableNewDslWithPagination.json");
const testdata = require("../../../../fixtures/testdata.json");
describe("Table Widget property pane feature validation", function() {
@@ -10,71 +9,205 @@ describe("Table Widget property pane feature validation", function() {
cy.addDsl(dsl);
});
+ // To be done:
+ // Column Data type: Video
+
+ it("Verify default array data", function() {
+ // Open property pane
+ cy.openPropertyPane("tablewidget");
+ // Open Widget side bar
+ cy.get(widgetsPage.addWidget).click();
+ // Drag and drop table widget
+ cy.dragAndDropToCanvas("tablewidget", { x: 300, y: 200 });
+ // close Widget side bar
+ cy.get(widgetsPage.closeWidgetBar).click({ force: true });
+ cy.wait(2000);
+ cy.SearchEntityandOpen("Table2");
+ // Verify default array data
+ cy.get(widgetsPage.tabedataField).should("not.be.empty");
+ cy.deleteWidget(widgetsPage.tableWidget);
+ cy.wait(2000);
+ });
+
+ it("Verify On Row Selected Action", function() {
+ // Open property pane
+ cy.openPropertyPane("tablewidget");
+ // Select show message in the "on selected row" dropdown
+ cy.onTableAction(0, "onrowselected", "Row is selected");
+ cy.PublishtheApp();
+ // Select 1st row
+ cy.isSelectRow(2);
+ cy.wait(2000);
+ // Verify Row is selected by showing the message
+ cy.get(commonlocators.toastmsg).contains("Row is selected");
+ cy.get(publish.backToEditor).click();
+ });
+
+ it("Check On Page Change Action", function() {
+ // Open property pane
+ cy.openPropertyPane("tablewidget");
+ // Select show message in the "on selected row" dropdown
+ cy.onTableAction(1, "onpagechange", "Page Changed");
+ cy.PublishtheApp();
+ cy.wait(2000);
+ // Change the page
+ cy.get(widgetsPage.nextPageButton).click({ force: true });
+ // Verify the page is changed
+ cy.get(commonlocators.toastmsg).contains("Page Changed");
+ cy.get(publish.backToEditor).click();
+ });
+ it("Verify On Search Text Change Action", function() {
+ // Open property pane
+ cy.openPropertyPane("tablewidget");
+ // Show Message on Search text change Action
+ cy.onTableAction(3, "onsearchtextchanged", "Search Text Changed");
+ cy.PublishtheApp();
+ // Change the Search text
+ cy.get(widgetsPage.searchField).type("Hello");
+ cy.wait(2000);
+ // Verify the search text is changed
+ cy.get(commonlocators.toastmsg).contains("Search Text Changed");
+ cy.get(publish.backToEditor).click();
+ });
+
+ it("Explore Widget related documents Verification", function() {
+ // Open property pane
+ cy.openPropertyPane("tablewidget");
+ // Click on "Explore widget related docs" button
+ cy.get(widgetsPage.exploreWidget).click();
+ // Verify the widget related document
+ cy.get(widgetsPage.widgetRelatedDocument).should("contain", "Table");
+ cy.wait(2000);
+ cy.get(widgetsPage.header).click();
+ cy.wait(1000);
+ cy.PublishtheApp();
+ });
+
it("Check open section and column data in property pane", function() {
cy.openPropertyPane("tablewidget");
+
+ // Validate the columns are visible in the property pane
cy.tableColumnDataValidation("id");
cy.tableColumnDataValidation("email");
cy.tableColumnDataValidation("userName");
cy.tableColumnDataValidation("productName");
cy.tableColumnDataValidation("orderAmount");
+
+ // Updating the column name ; "id" > "TestUpdated"
cy.tableColumnPopertyUpdate("id", "TestUpdated");
+
+ // Add new column in the table with name "CustomColumn"
cy.addColumn("CustomColumn");
+
cy.tableColumnDataValidation("customColumn1"); //To be updated later
+
+ // Hide all other columns
cy.hideColumn("email");
cy.hideColumn("userName");
cy.hideColumn("productName");
cy.hideColumn("orderAmount");
+
+ // Verifying the newly added column
cy.get(".draggable-header:contains('CustomColumn')").should("be.visible");
});
- it("Edit column name and validate test for computed value based on column type selected", function() {
+ it("Column Detail - Edit column name and validate test for computed value based on column type selected", function() {
+ cy.makeColumnVisible("email");
+ cy.makeColumnVisible("userName");
+ cy.makeColumnVisible("productName");
+ cy.makeColumnVisible("orderAmount");
+
+ // Open column detail to be edited by draggable id
cy.editColumn("id");
+ // Change the column name
cy.editColName("updatedId");
- cy.readTabledataPublish("1", "2").then((tabData) => {
+ // Reading single cell value of the table and verify it's value.
+ cy.readTabledataPublish("1", "1").then((tabData) => {
const tabValue = tabData;
+ cy.log(tabData);
expect(tabData).to.not.equal("2736212");
+ // Changing the Computed value from "id" to "Email"
cy.updateComputedValue(testdata.currentRowEmail);
- cy.readTabledataPublish("1", "2").then((tabData) => {
- expect(tabData).to.be.equal(tabValue);
- cy.log("computed value of plain text " + tabData);
+ // Reading single cell value of the table and verify it's value.
+ cy.readTabledataPublish("1", "0").then((tabData2) => {
+ cy.log(tabData2);
+ expect(tabData2).to.be.equal(tabValue);
+ cy.log("computed value of plain text " + tabData2);
});
});
+ // Changing Column data type from "Plain text" to "Number"
cy.changeColumnType("Number");
- cy.readTabledataPublish("1", "5").then((tabData) => {
- const tabValue = tabData;
+ cy.readTabledataPublish("1", "4").then((tabData) => {
+ cy.log(tabData);
expect(tabData).to.not.equal("lindsay.ferguson@reqres.in");
+ // Email to "orderAmount"
cy.updateComputedValue(testdata.currentRowOrderAmt);
- cy.readTabledataPublish("1", "0").then((tabData) => {
- expect(tabData).to.be.equal(tabValue);
- cy.log("computed value of number is " + tabData);
+ cy.readTabledataPublish("1", "0").then((tabData2) => {
+ cy.log(tabData2);
+ expect(tabData2).to.be.equal(tabData);
+ cy.log("computed value of number is " + tabData2);
});
});
+ // Changing Column data type from "Number" to "Date"
cy.changeColumnType("Date");
+ // orderAmout to "Moment Date"
cy.updateComputedValue(testdata.momentDate);
cy.readTabledataPublish("1", "1").then((tabData) => {
expect(tabData).to.not.equal("9.99");
cy.log("computed value of Date is " + tabData);
});
- // cy.changeColumnType("Time");
- // cy.updateComputedValue(testdata.momentDate);
- // cy.readTabledataPublish("1", "0").then(tabData => {
- // expect(tabData).to.not.equal("2736212");
- // cy.log("computed value of time is " + tabData);
- // });
+ // Changing Column data type from "URL" to "Video"
+ /* const videoVal = 'https://youtu.be/Sc-m3ceZyfk';
+ cy.changeColumnType("Video");
+ // "Moement "date" to "Video"
+ cy.updateComputedValue(videoVal);
+ // cy.testJson text("computedvalue", videoVal, )
+ // Verifying the href of the Video added.
+ cy.readTableLinkPublish("1", "1").then((hrefVal) => {
+ expect(hrefVal).to.be.equal(videoVal);
+ });*/
+
+ // Changing Column data type from "Date" to "Image"
+ const imageVal =
+ "https://images.pexels.com/photos/736230/pexels-photo-736230.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500";
+
+ cy.changeColumnType("Image");
+ // "Moement "date" to "Image"
+ cy.updateComputedValue(imageVal);
+ // Verifying the href of the image added.
+ cy.readTableLinkPublish("1", "0").then((hrefVal) => {
+ expect(hrefVal).to.be.contains(imageVal);
+ });
+
+ // Changing Column data type from "Date" to "URl"
+ cy.readTabledataPublish("1", "1").then(() => {
+ cy.changeColumnType("URL");
+ // "Image" to "url"
+ cy.updateComputedValue(testdata.currentRowEmail);
+ cy.readTabledataPublish("1", "0").then((tabData2) => {
+ expect(tabData2).to.not.equal("lindsay.ferguson@reqres.in");
+ cy.log("computed value of URL is " + tabData2);
+ });
+ });
});
it("Test to validate text allignment", function() {
+ // Verifying Center Alignment
cy.get(widgetsPage.centerAlign)
.first()
.click({ force: true });
- cy.readTabledataValidateCSS("0", "0", "justify-content", "center");
+ cy.readTabledataValidateCSS("1", "0", "justify-content", "center");
+
+ // Verifying Right Alignment
cy.get(widgetsPage.rightAlign)
.first()
.click({ force: true });
- cy.readTabledataValidateCSS("0", "0", "justify-content", "flex-end");
+ cy.readTabledataValidateCSS("1", "0", "justify-content", "flex-end");
+
+ // Verifying Left Alignment
cy.get(widgetsPage.leftAlign)
.first()
.click({ force: true });
@@ -82,19 +215,24 @@ describe("Table Widget property pane feature validation", function() {
});
it("Test to validate text format", function() {
+ // Validate Bold text
cy.get(widgetsPage.bold).click({ force: true });
- cy.readTabledataValidateCSS("0", "0", "font-weight", "700");
+ cy.readTabledataValidateCSS("1", "0", "font-weight", "700");
+ // Validate Italic text
cy.get(widgetsPage.italics).click({ force: true });
cy.readTabledataValidateCSS("0", "0", "font-style", "italic");
});
it("Test to validate vertical allignment", function() {
+ // Validate vertical alignemnt of Cell text to TOP
cy.get(widgetsPage.verticalTop).click({ force: true });
- cy.readTabledataValidateCSS("0", "0", "align-items", "flex-start");
+ cy.readTabledataValidateCSS("1", "0", "align-items", "flex-start");
+ // Validate vertical alignemnt of Cell text to Center
cy.get(widgetsPage.verticalCenter)
.last()
.click({ force: true });
- cy.readTabledataValidateCSS("0", "0", "align-items", "center");
+ cy.readTabledataValidateCSS("1", "0", "align-items", "center");
+ // Validate vertical alignemnt of Cell text to Bottom
cy.get(widgetsPage.verticalBottom)
.last()
.click({ force: true });
@@ -105,26 +243,30 @@ describe("Table Widget property pane feature validation", function() {
cy.get(widgetsPage.textColor)
.first()
.click({ force: true });
+ // Changing text color to GREEN and validate
cy.xpath(widgetsPage.greenColor).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(5000);
cy.wait("@updateLayout");
- cy.readTabledataValidateCSS("0", "0", "color", "rgb(3, 179, 101)");
+ cy.readTabledataValidateCSS("1", "0", "color", "rgb(3, 179, 101)");
+ // Changing text color to PURPLE and validate using JS
cy.get(widgetsPage.toggleJsColor).click();
cy.testCodeMirrorLast("purple");
cy.wait("@updateLayout");
- cy.readTabledataValidateCSS("0", "0", "color", "rgb(128, 0, 128)");
+ cy.readTabledataValidateCSS("1", "0", "color", "rgb(128, 0, 128)");
+ // Changing Cell backgroud color to GREEN and validate
cy.get(widgetsPage.backgroundColor)
.first()
.click({ force: true });
cy.xpath(widgetsPage.greenColor).click();
cy.wait("@updateLayout");
cy.readTabledataValidateCSS(
- "0",
+ "1",
"0",
"background",
"rgb(3, 179, 101) none repeat scroll 0% 0% / auto padding-box border-box",
);
+ // Changing Cell backgroud color to PURPLE and validate using JS
cy.get(widgetsPage.toggleJsBcgColor).click();
cy.testCodeMirrorLast("purple");
cy.wait("@updateLayout");
@@ -134,5 +276,50 @@ describe("Table Widget property pane feature validation", function() {
"background",
"rgb(128, 0, 128) none repeat scroll 0% 0% / auto padding-box border-box",
);
+ // close property pane
+ cy.closePropertyPane();
+ });
+
+ it("Verify default search text", function() {
+ // Open property pane
+ cy.openPropertyPane("tablewidget");
+ // Chage deat search text value to "data"
+ cy.testJsontext("defaultsearchtext", "data");
+ cy.PublishtheApp();
+ // Verify the deaullt search text
+ cy.get(widgetsPage.searchField).should("have.value", "data");
+ cy.get(publish.backToEditor).click();
+ });
+
+ it("Verify default selected row", function() {
+ // Open property pane
+ cy.openPropertyPane("tablewidget");
+ cy.testJsontext("defaultsearchtext", "");
+ // Change default selected row value to 1
+ cy.get(widgetsPage.defaultSelectedRowField).type("1");
+ cy.wait(2000);
+ cy.PublishtheApp();
+ // Verify the default selected row
+ cy.get(widgetsPage.selectedRow).should(
+ "have.css",
+ "background-color",
+ "rgba(106, 134, 206, 0.1)",
+ );
+ cy.get(publish.backToEditor).click();
+ });
+
+ it("Table-Delete Verification", function() {
+ // Open property pane
+ cy.openPropertyPane("tablewidget");
+ // Delete the Table widget
+ cy.deleteWidget(widgetsPage.tableWidget);
+ cy.PublishtheApp();
+ // Verify the Table widget is deleted
+ cy.get(widgetsPage.tableWidget).should("not.exist");
+ });
+
+ afterEach(() => {
+ // put your clean up code if any
+ cy.goToEditFromPublish();
});
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Property_JsonUpdate_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Property_JsonUpdate_spec.js
index 42509006f4f3..b6eda650dc85 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Property_JsonUpdate_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Property_JsonUpdate_spec.js
@@ -16,36 +16,52 @@ describe("Test Create Api and Bind to Table widget", function() {
});
it("Validate Table with API data and then add a column", function() {
+ // Open property pane
cy.SearchEntityandOpen("Table1");
+ // Change the table data to Apil data users
cy.testJsontext("tabledata", "{{Api1.data.users}}");
+ // Check server sided pagination
cy.CheckWidgetProperties(commonlocators.serverSidePaginationCheckbox);
+ // Open property pane of Text1
cy.SearchEntityandOpen("Text1");
+ // Change the text value to selected url
cy.testJsontext("text", "{{Table1.selectedRow.url}}");
+ // Open property pane
cy.SearchEntityandOpen("Table1");
+ // Copmre the table 1st index with itself
cy.readTabledata("0", "0").then((tabData) => {
const tableData = tabData;
localStorage.setItem("tableDataPage1", tableData);
});
+ // Validate the table 1st index
cy.ValidateTableData("1");
+ // Add new column
cy.addColumn("CustomColumn");
});
it("Update table json data and check the column names updated and validate empty value", function() {
+ // Open property pane
cy.SearchEntityandOpen("Table1");
+ // Change the table data
cy.testJsontext("tabledata", JSON.stringify(this.data.TableInputWithNull));
cy.wait("@updateLayout");
+ // Verify the columns are visible in property pane
cy.tableColumnDataValidation("id");
cy.tableColumnDataValidation("email");
cy.tableColumnDataValidation("userName");
cy.tableColumnDataValidation("productName");
cy.tableColumnDataValidation("orderAmount");
cy.tableColumnDataValidation("customColumn1");
+ // Hide the columns in the table from property pane
cy.hideColumn("id");
cy.hideColumn("email");
cy.hideColumn("userName");
cy.hideColumn("productName");
+ // Verify CustomColumn is visible
cy.get(".draggable-header:contains('CustomColumn')").should("be.visible");
+ // close property pane
cy.closePropertyPane();
+ // Validate the empty values
cy.readTabledataPublish("0", "0").then((tabData) => {
const tabValue = tabData;
expect(tabValue).to.be.equal("");
@@ -53,14 +69,19 @@ describe("Test Create Api and Bind to Table widget", function() {
});
it("Check Selected Row(s) Resets When Table Data Changes", function() {
+ // Select 1st row
cy.isSelectRow(1);
+ cy.openPropertyPane("tablewidget");
+ // Empty first row
cy.testJsontext("tabledata", "[]");
cy.wait("@updateLayout");
const newTableData = [...this.data.TableInput];
newTableData[0].userName = "";
+ // Change table data from empty to some
cy.testJsontext("tabledata", JSON.stringify(newTableData));
cy.wait("@updateLayout");
const selectedRowsSelector = `.t--widget-tablewidget .tbody .tr.selected-row`;
+ // Verify selected row resets on table data changes
cy.get(selectedRowsSelector).should(($p) => {
// should found 0 rows
expect($p).to.have.length(0);
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Add_button_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Add_button_spec.js
index 52c3d3f922ea..1152173a153c 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Add_button_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Add_button_spec.js
@@ -12,22 +12,30 @@ describe("Table Widget property pane feature validation", function() {
it("Table widget with Add button test and validation", function() {
cy.openPropertyPane("tablewidget");
+ // Open column details of "id".
cy.editColumn("id");
cy.get(widgetsPage.tableBtn).should("not.exist");
+ // Changing column data type to "Button"
cy.changeColumnType("Button");
+ // Changing the computed value (data) to "orderAmount"
cy.updateComputedValue(testdata.currentRowOrderAmt);
+ // Selecting button action to show message
cy.get(widgetsPage.actionSelect).click();
cy.get(commonlocators.chooseAction)
.children()
.contains("Show Message")
.click();
cy.addSuccessMessage("Successful ".concat(testdata.currentRowEmail));
+ // Close Property pane
cy.get(commonlocators.editPropCrossButton).click({ force: true });
+
+ // Validating the button action by clicking
cy.get(widgetsPage.tableBtn)
.last()
.click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(3000);
+ // Validating the toast message
cy.get(widgetsPage.toastAction).should("be.visible");
cy.get(widgetsPage.toastActionText)
.last()
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js
index 15a1cff83715..70734365ee2a 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Copy_Paste_spec.js
@@ -1,13 +1,8 @@
-const testdata = require("../../../../fixtures/testdata.json");
const apiwidget = require("../../../../locators/apiWidgetslocator.json");
-const explorer = require("../../../../locators/explorerlocators.json");
const commonlocators = require("../../../../locators/commonlocators.json");
-const formWidgetsPage = require("../../../../locators/FormWidgets.json");
-const publish = require("../../../../locators/publishWidgetspage.json");
const widgetsPage = require("../../../../locators/Widgets.json");
const dsl = require("../../../../fixtures/tableNewDsl.json");
-const pageid = "MyPage";
before(() => {
cy.addDsl(dsl);
});
@@ -47,7 +42,7 @@ describe("Test Suite to validate copy/paste table Widget", function() {
.last()
.click();
cy.get(apiwidget.propertyList).then(function($lis) {
- expect($lis).to.have.length(8);
+ expect($lis).to.have.length(9);
expect($lis.eq(0)).to.contain("{{Table1Copy.selectedRow}}");
expect($lis.eq(1)).to.contain("{{Table1Copy.selectedRows}}");
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Derived_Column_Computed_value_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Derived_Column_Computed_value_spec.js
index 1a927ec29247..e697923db3b5 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Derived_Column_Computed_value_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_Widget_Derived_Column_Computed_value_spec.js
@@ -12,19 +12,26 @@ describe("Table Widget property pane feature validation", function() {
it("Test to add column", function() {
cy.openPropertyPane("tablewidget");
+ // Adding new column
cy.addColumn("CustomColumn");
cy.tableColumnDataValidation("customColumn1"); //To be updated later
+ // Hiding all other columns in the table from property pane
cy.hideColumn("email");
cy.hideColumn("userName");
cy.hideColumn("productName");
cy.hideColumn("orderAmount");
+ // Validating the newly added column
cy.get(".draggable-header:contains('CustomColumn')").should("be.visible");
});
it("Edit column name and validate test for computed value", function() {
+ // Open column detail by draggable id of the column
cy.editColumn("customColumn1");
+ // Validating single cell value
cy.readTabledataPublish("1", "2").then(() => {
+ // Chaging the computed value to "Emails"
cy.updateComputedValue(testdata.currentRowWithIdOutside);
+ // Validating single cell value
cy.readTabledataPublish("1", "1").then((tabData) => {
expect(tabData).to.be.equal("#lindsay.ferguson@reqres.in");
cy.log("computed value of plain text " + tabData);
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js
index 673cdfdad4d7..f35121d00967 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Table_spec.js
@@ -36,11 +36,9 @@ describe("Table Widget Functionality", function() {
// .find("> .bp3-button-text")
// .should("have.text", "{{navigateTo()}}");
cy.get(commonlocators.editPropCrossButton).click({ force: true });
- cy.PublishtheApp();
});
it("Table Widget Functionality To Verify The Data", function() {
- cy.isSelectRow(1);
cy.readTabledataPublish("1", "3").then((tabData) => {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
@@ -49,7 +47,6 @@ describe("Table Widget Functionality", function() {
});
it("Table Widget Functionality To Show a Base64 Image", function() {
- cy.get(publish.backToEditor).click();
cy.openPropertyPane("tablewidget");
cy.editColumn("image");
cy.changeColumnType("Image");
@@ -57,8 +54,8 @@ describe("Table Widget Functionality", function() {
const index = 1;
const imageVal = this.data.TableInput[index].image;
- cy.readTableLinkPublish(index, "1").then((bgUrl) => {
- expect(bgUrl).to.be.equal(`url("${imageVal}")`);
+ cy.readTableLinkPublish(index, "1").then((hrefVal) => {
+ expect(hrefVal).to.contain(imageVal);
});
});
@@ -118,19 +115,18 @@ describe("Table Widget Functionality", function() {
.contains("is exactly")
.click();
cy.get(publish.inputValue).type(tabValue);
- cy.wait(500);
- cy.get(publish.applyFiltersBtn).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
- cy.get(publish.canvas)
- .first()
- .click();
+ cy.get(widgetsPage.filterApplyBtn).click({ force: true });
+ cy.wait(500);
+ // cy.get(widgetsPage.filterCloseBtn).click({force:true});
cy.readTabledataPublish("0", "3").then((tabData) => {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
});
+ cy.get(widgetsPage.filterCloseBtn).click({ force: true });
+ cy.get(publish.filterBtn).click();
cy.get(publish.removeFilter).click();
- cy.get("body").type("{esc}");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
@@ -139,7 +135,8 @@ describe("Table Widget Functionality", function() {
});
cy.get(publish.canvas)
.first()
- .click();
+ .click({ force: true });
+ cy.wait(500);
});
});
@@ -159,19 +156,17 @@ describe("Table Widget Functionality", function() {
.contains("contains")
.click();
cy.get(publish.inputValue).type("Lindsay");
- cy.wait(500);
- cy.get(publish.applyFiltersBtn).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
- cy.get(publish.canvas)
- .first()
- .click();
+ cy.get(widgetsPage.filterApplyBtn).click({ force: true });
+ cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
});
+ cy.get(widgetsPage.filterCloseBtn).click({ force: true });
+ cy.get(publish.filterBtn).click();
cy.get(publish.removeFilter).click();
- cy.get("body").type("{esc}");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
@@ -180,7 +175,7 @@ describe("Table Widget Functionality", function() {
});
cy.get(publish.canvas)
.first()
- .click();
+ .click({ force: true });
});
});
@@ -200,19 +195,17 @@ describe("Table Widget Functionality", function() {
.contains("starts with")
.click();
cy.get(publish.inputValue).type("Lindsay");
- cy.wait(500);
- cy.get(publish.applyFiltersBtn).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
- cy.get(publish.canvas)
- .first()
- .click();
+ cy.get(widgetsPage.filterApplyBtn).click({ force: true });
+ cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
});
+ cy.get(widgetsPage.filterCloseBtn).click({ force: true });
+ cy.get(publish.filterBtn).click();
cy.get(publish.removeFilter).click();
- cy.get("body").type("{esc}");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
@@ -221,7 +214,7 @@ describe("Table Widget Functionality", function() {
});
cy.get(publish.canvas)
.first()
- .click();
+ .click({ force: true });
});
});
@@ -241,19 +234,17 @@ describe("Table Widget Functionality", function() {
.contains("ends with")
.click();
cy.get(publish.inputValue).type("Ferguson");
- cy.wait(500);
- cy.get(publish.applyFiltersBtn).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
- cy.get(publish.canvas)
- .first()
- .click();
+ cy.get(widgetsPage.filterApplyBtn).click({ force: true });
+ cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
const tabValue = tabData;
expect(tabValue).to.be.equal("Lindsay Ferguson");
});
+ cy.get(widgetsPage.filterCloseBtn).click({ force: true });
+ cy.get(publish.filterBtn).click();
cy.get(publish.removeFilter).click();
- cy.get("body").type("{esc}");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.readTabledataPublish("0", "3").then((tabData) => {
@@ -262,7 +253,7 @@ describe("Table Widget Functionality", function() {
});
cy.get(publish.canvas)
.first()
- .click();
+ .click({ force: true });
});
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Multiple_Widgets_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Multiple_Widgets_spec.js
index 4eda944815d2..44df894d66bc 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Multiple_Widgets_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Multiple_Widgets_spec.js
@@ -38,7 +38,7 @@ describe("Entity explorer tests related to widgets and validation", function() {
.last()
.click({ force: true });
cy.get(apiwidget.propertyList).then(function($lis) {
- expect($lis).to.have.length(8);
+ expect($lis).to.have.length(9);
expect($lis.eq(0)).to.contain("{{Table1.selectedRow}}");
expect($lis.eq(1)).to.contain("{{Table1.selectedRows}}");
expect($lis.eq(2)).to.contain("{{Table1.selectedRowIndex}}");
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js
index 2a4774093a37..0a3b1c96ea21 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_Widgets_Copy_Paste_Delete_Undo_Keyboard_Event_spec.js
@@ -5,7 +5,7 @@ const commonlocators = require("../../../../locators/commonlocators.json");
const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const widgetsPage = require("../../../../locators/Widgets.json");
-const dsl = require("../../../../fixtures/formWidgetdsl.json");
+const dsl = require("../../../../fixtures/formWithInputdsl.json");
const pageid = "MyPage";
before(() => {
@@ -51,6 +51,22 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() {
expect($lis).to.have.length(2);
expect($lis.eq(0)).to.contain("{{FormTestCopy.isVisible}}");
expect($lis.eq(1)).to.contain("{{FormTestCopy.data}}");
+ cy.contains("FormTestCopy");
+ cy.get($lis.eq(1))
+ .contains("{{FormTestCopy.data}}")
+ .click({ force: true });
+ //cy.get('.clipboard-message success')
+ // .contains('Copied to clipboard!')
+ // .should('be.visible');
+ cy.wait(10000);
+ cy.GlobalSearchEntity("Input1");
+ cy.wait(10000);
+ cy.get(".bp3-input")
+ .first()
+ .click({ force: true });
+ cy.get(".bp3-input")
+ .first()
+ .type(`{${modifierKey}}v`, { force: true });
});
});
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js
index d96a598d7a5f..8a2e23e43627 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Button_spec.js
@@ -19,12 +19,20 @@ describe("Button Widget Functionality", function() {
it("Button-Style Validation", function() {
//Changing the style of the button from the property pane and verify it's color.
+ // Change to Warning button sytle
+ cy.changeButtonStyle(2, "rgb(254, 184, 17)", "rgba(0, 0, 0, 0)");
+ cy.get(publishPage.backToEditor).click({ force: true });
+ cy.openPropertyPane("buttonwidget");
+ // Change to Info button sytle
+ cy.changeButtonStyle(4, "rgb(102, 152, 255)", "rgba(0, 0, 0, 0)");
+ cy.get(publishPage.backToEditor).click({ force: true });
+ cy.openPropertyPane("buttonwidget");
// Change to Secondary button sytle
- cy.changeButtonStyle(2, "rgba(0, 0, 0, 0)", "rgba(0, 0, 0, 0)");
+ cy.changeButtonStyle(5, "rgb(133, 130, 130)", "rgba(0, 0, 0, 0)");
cy.get(publishPage.backToEditor).click({ force: true });
// Change to Danger button sytle
cy.openPropertyPane("buttonwidget");
- cy.changeButtonStyle(3, "rgb(179, 3, 56)", "rgb(139, 2, 43)");
+ cy.changeButtonStyle(3, "rgb(242, 43, 43)", "rgb(139, 2, 43)");
cy.get(publishPage.backToEditor).click({ force: true });
// Change to Primary button sytle
cy.openPropertyPane("buttonwidget");
@@ -150,7 +158,6 @@ describe("Button Widget Functionality", function() {
});
it("Button-Copy Verification", function() {
- const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl";
//Copy button and verify all properties
cy.copyWidget("buttonwidget", widgetsPage.buttonWidget);
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_With_Switch_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_With_Switch_spec.js
index 503ad63a11ed..0145d8cedc95 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_With_Switch_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_With_Switch_spec.js
@@ -4,6 +4,7 @@ const widgetsPage = require("../../../../locators/Widgets.json");
const publish = require("../../../../locators/publishWidgetspage.json");
const dsl = require("../../../../fixtures/datepicker_switchDsl.json");
const pages = require("../../../../locators/Pages.json");
+const dayjs = require("dayjs");
describe("Switch Widget within Form widget Functionality", function() {
before(() => {
@@ -28,9 +29,7 @@ describe("Switch Widget within Form widget Functionality", function() {
cy.get(formWidgetsPage.defaultDate).click();
cy.SetDateToToday();
cy.setDate(1, "ddd MMM DD YYYY");
- const nextDay = Cypress.moment()
- .add(1, "days")
- .format("DD/MM/YYYY");
+ const nextDay = dayjs().format("DD/MM/YYYY");
cy.log(nextDay);
cy.get(widgetsPage.actionSelect).click();
cy.get(commonlocators.chooseAction)
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_spec.js
index 3a1a83a28ee0..72bbc757503b 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/DatePicker_spec.js
@@ -3,6 +3,7 @@ const formWidgetsPage = require("../../../../locators/FormWidgets.json");
const dsl = require("../../../../fixtures/newFormDsl.json");
const publishPage = require("../../../../locators/publishWidgetspage.json");
const pages = require("../../../../locators/Pages.json");
+const dayjs = require("dayjs");
describe("DatePicker Widget Functionality", function() {
before(() => {
@@ -34,7 +35,7 @@ describe("DatePicker Widget Functionality", function() {
* @param2 --> user date formate
*/
cy.setDate(1, "ddd MMM DD YYYY");
- const nextDay = Cypress.moment()
+ const nextDay = dayjs()
.add(1, "days")
.format("DD/MM/YYYY");
cy.log(nextDay);
@@ -51,7 +52,7 @@ describe("DatePicker Widget Functionality", function() {
});
it("Datepicker-Clear date validation", function() {
- const today = Cypress.moment()
+ const today = dayjs()
.add(0, "days")
.format("DD/MM/YYYY");
cy.get(formWidgetsPage.defaultDate).click();
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js
index 70cef20567ad..c49cfbb54618 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js
@@ -4,11 +4,49 @@ const publish = require("../../../../locators/publishWidgetspage.json");
const dsl = require("../../../../fixtures/formdsl.json");
const pages = require("../../../../locators/Pages.json");
const widgetsPage = require("../../../../locators/Widgets.json");
+const explorer = require("../../../../locators/explorerlocators.json");
describe("Form Widget Functionality", function() {
before(() => {
cy.addDsl(dsl);
});
+ it("Defult Form text, Reset and Close button Validation", function() {
+ cy.get(widgetsPage.textWidget).should("be.visible");
+ cy.get(widgetsPage.formButtonWidget)
+ .contains("Submit")
+ .scrollIntoView()
+ .should("be.visible");
+ cy.get(widgetsPage.formButtonWidget)
+ .contains("Reset")
+ .scrollIntoView()
+ .should("be.visible");
+ });
+ it("Add Multiple widgets in Form", function() {
+ cy.get(explorer.addWidget).click();
+ cy.get(commonlocators.entityExplorersearch).should("be.visible");
+ cy.dragAndDropToWidget("multiselectwidget", "formwidget", {
+ x: 100,
+ y: 100,
+ });
+ cy.dragAndDropToWidget("inputwidget", "formwidget", { x: 50, y: 200 });
+ cy.get(formWidgetsPage.multiselectWidget).should("be.visible");
+ cy.get(widgetsPage.inputWidget).should("be.visible");
+ cy.PublishtheApp();
+ });
+ it("Form_Widget Minimize and maximize General Validation", function() {
+ cy.openPropertyPane("formwidget");
+ cy.get(commonlocators.generalChevran).click({ force: true });
+ cy.get(commonlocators.generalSection).should("not.be.visible");
+ cy.get(commonlocators.generalChevran).click({ force: true });
+ cy.get(commonlocators.generalSection).should("be.visible");
+ cy.PublishtheApp();
+ });
+ it("Rename Form widget from Entity Explorer", function() {
+ cy.GlobalSearchEntity("Form1");
+ cy.RenameEntity("Form");
+ cy.wait(1000);
+ cy.get(".t--entity").should("contain", "Form");
+ });
it("Form Widget Functionality", function() {
cy.openPropertyPane("formwidget");
/**
@@ -39,15 +77,14 @@ describe("Form Widget Functionality", function() {
.scrollTo("bottom")
.should("be.visible");
cy.get(commonlocators.editPropCrossButton).click({ force: true });
- cy.PublishtheApp();
});
it("Form Widget Functionality To Verify The Colour", function() {
+ cy.PublishtheApp();
cy.get(formWidgetsPage.formD)
.should("have.css", "background-color")
.and("eq", "rgb(3, 179, 101)");
});
it("Form Widget Functionality To Unchecked Visible Widget", function() {
- cy.get(publish.backToEditor).click();
cy.openPropertyPane("formwidget");
cy.togglebarDisable(commonlocators.visibleCheckbox);
cy.PublishtheApp();
@@ -61,7 +98,51 @@ describe("Form Widget Functionality", function() {
cy.get(publish.formWidget).should("be.visible");
cy.get(publish.backToEditor).click();
});
+ it("Toggle JS - Form-Unckeck Visible field Validation", function() {
+ cy.openPropertyPane("formwidget");
+ //Uncheck the disabled checkbox using JS and validate
+ cy.get(widgetsPage.toggleVisible).click({ force: true });
+ cy.testJsontext("visible", "false");
+ cy.PublishtheApp();
+ cy.get(publish.formWidget).should("not.exist");
+ });
+
+ it("Toggle JS - Form-Check Visible field Validation", function() {
+ cy.openPropertyPane("formwidget");
+ //Check the disabled checkbox using JS and Validate
+ cy.testJsontext("visible", "true");
+ cy.PublishtheApp();
+ cy.get(publish.formWidget).should("be.visible");
+ });
+ it("Explore Widget related documents Verification", function() {
+ // Open property pane
+ cy.openPropertyPane("formwidget");
+ // Click on "Explore widget related docs" button
+ cy.get(widgetsPage.exploreWidget).click();
+ // Verify the widget related document
+ cy.get(widgetsPage.widgetRelatedDocument).should("contain", "Form");
+ cy.wait(500);
+ cy.get(widgetsPage.header).click();
+ cy.PublishtheApp();
+ });
+ it("Form-Copy Verification", function() {
+ cy.openPropertyPane("formwidget");
+ const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl";
+ //Copy Form and verify all properties
+ cy.copyWidget("formwidget", widgetsPage.formWidget);
+
+ cy.PublishtheApp();
+ });
+
+ it("Form-Delete Verification", function() {
+ cy.openPropertyPane("formwidget");
+ // Delete the Form widget
+ cy.deleteWidget(widgetsPage.formWidget);
+ cy.PublishtheApp();
+ cy.get(widgetsPage.formWidget).should("not.exist");
+ });
});
afterEach(() => {
// put your clean up code if any
+ cy.goToEditFromPublish();
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Input_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Input_spec.js
index f062d97d4622..169ccdb8079e 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Input_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/Input_spec.js
@@ -159,6 +159,19 @@ describe("Input Widget Functionality", function() {
});
});
+ it("Input Functionality To check phone number input type", function() {
+ // cy.openPropertyPane("inputwidget");
+ cy.get(widgetsPage.innertext)
+ .click()
+ .clear();
+ cy.selectDropdownValue(commonlocators.dataType, "Phone Number");
+ cy.get(commonlocators.inputCountryCodeChangeType)
+ .invoke("text")
+ .then((text) => {
+ expect(text).to.equal("🇺🇸+1");
+ });
+ });
+
it("Input label wrapper do not show if lable and tooltip is empty", () => {
cy.get(".t--input-label-wrapper").should("not.exist");
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GenerateCRUD/GenerateCRUDPage_Spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GenerateCRUD/GenerateCRUDPage_Spec.js
index edd1694e3c6f..da151ed02560 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GenerateCRUD/GenerateCRUDPage_Spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GenerateCRUD/GenerateCRUDPage_Spec.js
@@ -4,9 +4,15 @@ import homePage from "../../../../locators/HomePage.json";
import datasource from "../../../../locators/DatasourcesEditor.json";
describe("Generate New CRUD Page Inside from entity explorer", function() {
+ let datasourceName;
+
before(() => {
cy.startRoutesForDatasource();
cy.createPostgresDatasource();
+
+ cy.get("@createDatasource").then((httpResponse) => {
+ datasourceName = httpResponse.response.body.data.name;
+ });
// TODO
// 1. Add INVALID credential for a datasource and test the invalid datasource structure flow.
// 2. Add 2 supported datasource and 1 not supported datasource with a fixed name to search.
@@ -27,7 +33,7 @@ describe("Generate New CRUD Page Inside from entity explorer", function() {
cy.get(generatePage.selectDatasourceDropdown).click();
cy.get(generatePage.datasourceDropdownOption)
- .first()
+ .contains(datasourceName)
.click();
cy.wait("@getDatasourceStructure").should(
@@ -81,7 +87,8 @@ describe("Generate New CRUD Page Inside from entity explorer", function() {
cy.fillPostgresDatasourceForm();
cy.generateUUID().then((UUID) => {
- cy.renameDatasource(`PostgresSQL CRUD Demo ${UUID}`);
+ datasourceName = `PostgresSQL CRUD Demo ${UUID}`;
+ cy.renameDatasource(datasourceName);
});
cy.startRoutesForDatasource();
@@ -124,10 +131,16 @@ describe("Generate New CRUD Page Inside from entity explorer", function() {
cy.get(pages.integrationActiveTab)
.should("be.visible")
.click({ force: true });
+ cy.wait(1000);
- cy.get(generatePage.datasourceCardGeneratePageBtn)
- .first()
- .click();
+ cy.get(datasource.datasourceCard)
+ .contains(datasourceName)
+ .scrollIntoView()
+ .should("be.visible")
+ .closest(datasource.datasourceCard)
+ .within(() => {
+ cy.get(datasource.datasourceCardGeneratePageBtn).click();
+ });
cy.wait("@getDatasourceStructure").should(
"have.nested.property",
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GenerateCRUD/S3_CRUDPage_Spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GenerateCRUD/S3_CRUDPage_Spec.js
new file mode 100644
index 000000000000..c549ad09f02d
--- /dev/null
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/GenerateCRUD/S3_CRUDPage_Spec.js
@@ -0,0 +1,62 @@
+const pages = require("../../../../locators/Pages.json");
+const generatePage = require("../../../../locators/GeneratePage.json");
+
+describe("Generate New CRUD Page Inside from entity explorer", function() {
+ let datasourceName;
+ before(() => {
+ cy.startRoutesForDatasource();
+ cy.createAmazonS3Datasource();
+
+ cy.get("@createDatasource").then((httpResponse) => {
+ datasourceName = httpResponse.response.body.data.name;
+ });
+ });
+
+ it("Add new Page and generate CRUD template using existing supported datasource", function() {
+ cy.get(pages.AddPage)
+ .first()
+ .click();
+ cy.wait("@createPage").should(
+ "have.nested.property",
+ "response.body.responseMeta.status",
+ 201,
+ );
+
+ cy.get(generatePage.generateCRUDPageActionCard).click();
+
+ cy.get(generatePage.selectDatasourceDropdown).click();
+
+ cy.get(generatePage.datasourceDropdownOption)
+ .contains(datasourceName)
+ .click();
+
+ // fetch bucket
+ cy.wait("@datasourceQuery").should(
+ "have.nested.property",
+ "response.body.responseMeta.status",
+ 200,
+ );
+
+ cy.get(generatePage.selectTableDropdown).click();
+
+ cy.get(generatePage.dropdownOption)
+ .contains("assets-test.appsmith.com")
+ .scrollIntoView()
+ .should("be.visible")
+ .click();
+ // skip optional search column selection.
+ cy.get(generatePage.generatePageFormSubmitBtn).click();
+
+ cy.wait("@replaceLayoutWithCRUDPage").should(
+ "have.nested.property",
+ "response.body.responseMeta.status",
+ 201,
+ );
+ cy.wait("@getActions");
+ cy.wait("@postExecute").should(
+ "have.nested.property",
+ "response.body.responseMeta.status",
+ 200,
+ );
+ });
+});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js
index 620b05e0aa66..27fba41ba240 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js
@@ -1,37 +1,68 @@
const homePage = require("../../../../locators/HomePage.json");
+const dsl = require("../../../../fixtures/displayWidgetDsl.json");
describe("Organization Import Application", function() {
let orgid;
let newOrganizationName;
- const fixtureDummyAppPath = "application-file.json";
+ let appname;
+
+ before(() => {
+ cy.addDsl(dsl);
+ });
+
it("Can Import Application", function() {
cy.NavigateToHome();
- cy.generateUUID().then((uid) => {
- orgid = uid;
- localStorage.setItem("OrgName", orgid);
- cy.createOrg();
- cy.wait("@createOrg").then((createOrgInterception) => {
- newOrganizationName = createOrgInterception.response.body.data.name;
- cy.renameOrg(newOrganizationName, orgid);
- cy.get(homePage.orgImportAppOption).click({ force: true });
+ appname = localStorage.getItem("AppName");
+ cy.get(homePage.searchInput).type(appname);
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
+ cy.wait(2000);
+
+ cy.get(homePage.applicationCard)
+ .first()
+ .trigger("mouseover");
+ cy.get(homePage.appMoreIcon)
+ .first()
+ .click({ force: true });
+ cy.get(homePage.exportAppFromMenu).click({ force: true });
+ cy.get(homePage.searchInput).clear();
+ cy.get(`a[id=t--export-app-link]`).then((anchor) => {
+ const url = anchor.prop("href");
+ cy.request(url).then(({ body, headers }) => {
+ expect(headers).to.have.property("content-type", "application/json");
+ expect(headers).to.have.property(
+ "content-disposition",
+ `attachment; filename*=UTF-8''${appname}.json`,
+ );
+ cy.writeFile("cypress/fixtures/exported-app.json", body, "utf-8");
+
+ cy.generateUUID().then((uid) => {
+ orgid = uid;
+ localStorage.setItem("OrgName", orgid);
+ cy.createOrg();
+ cy.wait("@createOrg").then((createOrgInterception) => {
+ newOrganizationName = createOrgInterception.response.body.data.name;
+ cy.renameOrg(newOrganizationName, orgid);
+ cy.get(homePage.orgImportAppOption).click({ force: true });
- cy.get(homePage.orgImportAppModal).should("be.visible");
- cy.xpath(homePage.uploadLogo).attachFile(fixtureDummyAppPath);
+ cy.get(homePage.orgImportAppModal).should("be.visible");
+ cy.xpath(homePage.uploadLogo).attachFile("exported-app.json");
- cy.get(homePage.orgImportAppButton).click({ force: true });
- cy.wait("@importNewApplication").then((interception) => {
- let appId = interception.response.body.data.id;
- let defaultPage = interception.response.body.data.pages.find(
- (eachPage) => !!eachPage.isDefault,
- );
- cy.get(homePage.toastMessage).should(
- "contain",
- "Application imported successfully",
- );
- cy.url().should(
- "include",
- `/applications/${appId}/pages/${defaultPage.id}/edit`,
- );
+ cy.get(homePage.orgImportAppButton).click({ force: true });
+ cy.wait("@importNewApplication").then((interception) => {
+ let appId = interception.response.body.data.id;
+ let defaultPage = interception.response.body.data.pages.find(
+ (eachPage) => !!eachPage.isDefault,
+ );
+ cy.get(homePage.toastMessage).should(
+ "contain",
+ "Application imported successfully",
+ );
+ cy.url().should(
+ "include",
+ `/applications/${appId}/pages/${defaultPage.id}/edit`,
+ );
+ });
+ });
});
});
});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MySQL_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MySQL_spec.js
new file mode 100644
index 000000000000..36671403cae3
--- /dev/null
+++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MySQL_spec.js
@@ -0,0 +1,56 @@
+const datasource = require("../../../../locators/DatasourcesEditor.json");
+const queryEditor = require("../../../../locators/QueryEditor.json");
+const datasourceEditor = require("../../../../locators/DatasourcesEditor.json");
+
+let datasourceName;
+
+describe("MySQL datasource test cases", function() {
+ beforeEach(() => {
+ cy.startRoutesForDatasource();
+ });
+
+ it("Create, test, save then delete a MySQL datasource", function() {
+ cy.NavigateToDatasourceEditor();
+ cy.get(datasource.MySQL).click();
+ cy.getPluginFormsAndCreateDatasource();
+ cy.fillMySQLDatasourceForm();
+ cy.get("@createDatasource").then((httpResponse) => {
+ datasourceName = httpResponse.response.body.data.name;
+ });
+ cy.testSaveDatasource();
+ });
+
+ it("Create with trailing white spaces in host address and database name, test, save then delete a MySQL datasource", function() {
+ cy.NavigateToDatasourceEditor();
+ cy.get(datasource.MySQL).click();
+ cy.getPluginFormsAndCreateDatasource();
+ cy.fillMySQLDatasourceForm(true);
+ cy.get("@createDatasource").then((httpResponse) => {
+ datasourceName = httpResponse.response.body.data.name;
+ });
+ cy.testSaveDatasource();
+ });
+
+ it("Create a new query from the datasource editor", function() {
+ cy.saveDatasource();
+ // cy.get(datasource.createQuerty).click();
+ cy.get(`${datasourceEditor.datasourceCard} ${datasource.createQuerty}`)
+ .last()
+ .click();
+ cy.wait("@createNewApi").should(
+ "have.nested.property",
+ "response.body.responseMeta.status",
+ 201,
+ );
+
+ cy.get(queryEditor.queryMoreAction).click();
+ cy.get(queryEditor.deleteUsingContext).click();
+ cy.wait("@deleteAction").should(
+ "have.nested.property",
+ "response.body.responseMeta.status",
+ 200,
+ );
+
+ cy.deleteDatasource(datasourceName);
+ });
+});
diff --git a/app/client/cypress/locators/DatasourcesEditor.json b/app/client/cypress/locators/DatasourcesEditor.json
index e292a0c5fb1c..54476e1426d0 100644
--- a/app/client/cypress/locators/DatasourcesEditor.json
+++ b/app/client/cypress/locators/DatasourcesEditor.json
@@ -10,12 +10,15 @@
"MongoDB": ".t--plugin-name:contains('MongoDB')",
"RESTAPI": ".t--plugin-name:contains('REST API')",
"PostgreSQL": ".t--plugin-name:contains('PostgreSQL')",
+ "MySQL": ".t--plugin-name:contains('Mysql')",
"sectionAuthentication": "[data-cy=section-Authentication]",
"PostgresEntity": ".t--entity-name:contains(PostgreSQL)",
+ "MySQLEntity": ".t--entity-name:contains(Mysql)",
"createQuerty": ".t--create-query",
"activeDatasourceList": ".t--active-datasource-list",
"datasourceCard": ".t--datasource",
"datasourceCardMenu": ".t--datasource-menu-option",
+ "datasourceCardGeneratePageBtn": ".t--generate-template",
"datasourceMenuOptionEdit": "t--datasource-option-edit",
"datasourceMenuOptionDelete":"t--datasource-option-delete",
"editDatasource": ".t--edit-datasource",
@@ -30,7 +33,7 @@
"MsSQL": ".t--plugin-name:contains('MsSQL')",
"Firestore": ".t--plugin-name:contains('Firestore')",
"Redshift": ".t--plugin-name:contains('Redshift')",
- "AmazonS3": ".t--plugin-name:contains('Amazon S3')",
+ "AmazonS3": ".t--plugin-name:contains('S3')",
"authType": "[data-cy=authType]",
"OAuth2": "//div[contains(@class,'option') and text()='OAuth 2.0']",
"accessTokenUrl": "[data-cy='authentication.accessTokenUrl'] input",
diff --git a/app/client/cypress/locators/Debugger.json b/app/client/cypress/locators/Debugger.json
index 9e5cda0e4c90..4a3a4c2829b6 100644
--- a/app/client/cypress/locators/Debugger.json
+++ b/app/client/cypress/locators/Debugger.json
@@ -1,4 +1,5 @@
{
"debuggerIcon": ".t--debugger",
- "debuggerLogState": ".t--debugger-log-state"
+ "debuggerLogState": ".t--debugger-log-state",
+ "errorCount": ".t--debugger-count"
}
\ No newline at end of file
diff --git a/app/client/cypress/locators/GeneratePage.json b/app/client/cypress/locators/GeneratePage.json
index f2326fa67259..68708b082bea 100644
--- a/app/client/cypress/locators/GeneratePage.json
+++ b/app/client/cypress/locators/GeneratePage.json
@@ -6,6 +6,5 @@
"selectTableDropdown":"[data-cy=t--table-dropdown]",
"dropdownOption": ".bp3-popover-content .t--dropdown-option",
"selectSearchColumnDropdown":"[data-cy=t--searchColumn-dropdown]",
- "generatePageFormSubmitBtn":"[data-cy=t--generate-page-form-submit]",
- "datasourceCardGeneratePageBtn": ".t--generate-template"
+ "generatePageFormSubmitBtn":"[data-cy=t--generate-page-form-submit]"
}
\ No newline at end of file
diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json
index 70fb968d96a2..94525ba54df0 100644
--- a/app/client/cypress/locators/Widgets.json
+++ b/app/client/cypress/locators/Widgets.json
@@ -110,5 +110,22 @@
"switchWidgetInactive": ".t--switch-widget-inactive",
"switchWidgetLoading": ".t--switch-widget-loading",
"chartPlotGroup": "g.raphael-group-63-plot-group",
- "toggleEnableMultirowselection": ".t--property-control-enablemultirowselection .bp3-control-indicator"
+ "toggleEnableMultirowselection": ".t--property-control-enablemultirowselection .bp3-control-indicator",
+ "formWidget": ".t--draggable-formwidget",
+ "searchField": "[type=search]",
+ "defaultSelectedRowField":".t--property-control-defaultselectedrow .CodeMirror-line",
+ "selectedRow":".selected-row",
+ "removeTableWidget": ".t--delete-widget",
+ "nextPageButton": ".t--table-widget-next-page",
+ "addWidget": ".t--entity.group.widgets > div:nth-child(1) >div:nth-child(5)",
+ "closeWidgetBar": ".t--close-widgets-sidebar",
+ "tabedataField": ".t--property-control-tabledata",
+ "exploreWidget": "[class$=bp3-panel-stack-view] > div:nth-child(1) > div:nth-child(1) > span:nth-child(4)",
+ "widgetRelatedDocument": "div.main > div:nth-child(1) > div:nth-child(1)",
+ "rowHeight":".t--table-compact-mode-toggle-btn",
+ "rowHeightShortOpt":".bp3-popover-content > div > div:nth-child(1)",
+ "tbIndex0": "[class=td][data-colindex='0'][data-rowindex='0']",
+ "filterApplyBtn":".t--apply-filter-btn",
+ "filterCloseBtn":".t--close-filter-btn",
+ "header":"#header-root"
}
diff --git a/app/client/cypress/locators/commonlocators.json b/app/client/cypress/locators/commonlocators.json
index f8f20361e669..f4c055aa0b94 100644
--- a/app/client/cypress/locators/commonlocators.json
+++ b/app/client/cypress/locators/commonlocators.json
@@ -116,6 +116,9 @@
"decimalType": ".t--property-control-decimals .bp3-popover-target",
"allowCurrencyChange": ".t--property-control-allowcurrencychange input[type='checkbox']",
"inputCurrencyChangeType": ".t--input-currency-change",
+ "inputCountryCodeChangeType": ".t--input-country-code-change",
"viewerPage": ".t--app-viewer-page",
- "dropDownOptSelected": "//span[@type='p1']"
+ "dropDownOptSelected": "//span[@type='p1']",
+ "generalChevran":".t--property-pane-section-collapse-general [icon=chevron-right]",
+ "generalSection":".t--property-pane-section-general"
}
diff --git a/app/client/cypress/setup-test.sh b/app/client/cypress/setup-test.sh
index abb2a006a3e9..6b55ad5cb370 100755
--- a/app/client/cypress/setup-test.sh
+++ b/app/client/cypress/setup-test.sh
@@ -35,6 +35,14 @@ sudo docker run --network host --name postgres -d -p 5432:5432 \
--health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 \
postgres:latest &
+sudo docker run -p 127.0.0.1:3306:3306 --name mariadb -e MARIADB_ROOT_PASSWORD=root123 -d mariadb
+
+echo "Sleeping for 30 seconds to let the MySQL start"
+sleep 30
+
+sudo docker exec -i mariadb mysql -uroot -proot123 mysql < `pwd`/cypress/init-mysql-dump-for-test.sql
+
+
echo "Sleeping for 30 seconds to let the servers start"
sleep 30
@@ -100,4 +108,4 @@ curl -k --request POST -v 'https://dev.appsmith.com/api/v1/users' \
# DEBUG=cypress:* $(npm bin)/cypress version
# sed -i -e "s|api_url:.*$|api_url: $CYPRESS_URL|g" /github/home/.cache/Cypress/4.1.0/Cypress/resources/app/packages/server/config/app.yml
-# cat /github/home/.cache/Cypress/4.1.0/Cypress/resources/app/packages/server/config/app.yml
\ No newline at end of file
+# cat /github/home/.cache/Cypress/4.1.0/Cypress/resources/app/packages/server/config/app.yml
diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js
index 96a585432a72..f0e91871ae7b 100644
--- a/app/client/cypress/support/commands.js
+++ b/app/client/cypress/support/commands.js
@@ -3,6 +3,7 @@
/* eslint-disable cypress/no-assigning-return-values */
require("cypress-file-upload");
+const dayjs = require("dayjs");
const loginPage = require("../locators/LoginPage.json");
const homePage = require("../locators/HomePage.json");
@@ -936,7 +937,6 @@ Cypress.Commands.add("CreationOfUniqueAPIcheck", (apiname) => {
.type(apiname, { force: true, delay: 500 })
.should("have.value", apiname);
cy.get(".error-message").should(($x) => {
- console.log($x);
expect($x).contain(apiname.concat(" is already being used."));
});
cy.get(apiwidget.apiTxt).blur();
@@ -1022,14 +1022,12 @@ Cypress.Commands.add("CreateApiAndValidateUniqueEntityName", (apiname) => {
.type(apiname, { force: true })
.should("have.value", apiname);
cy.get(".t--nameOfApi .error-message").should(($x) => {
- console.log($x);
expect($x).contain(apiname.concat(" is already being used."));
});
});
Cypress.Commands.add("validateMessage", (value) => {
cy.get(".bp3-popover-content").should(($x) => {
- console.log($x);
expect($x).contain(value.concat(" is already being used."));
});
});
@@ -1474,6 +1472,13 @@ Cypress.Commands.add("showColumn", (colId) => {
.should("be.visible");
});
+Cypress.Commands.add("makeColumnVisible", (colId) => {
+ cy.get("[data-rbd-draggable-id='" + colId + "'] .t--show-column-btn").click({
+ force: true,
+ });
+ cy.wait(1000);
+});
+
Cypress.Commands.add("addColumn", (colId) => {
cy.get(widgetsPage.addColumn).scrollIntoView();
cy.get(widgetsPage.addColumn)
@@ -1531,7 +1536,18 @@ Cypress.Commands.add("addAction", (value) => {
cy.enterActionValue(value);
});
-Cypress.Commands.add("selectShowMsg", (value) => {
+Cypress.Commands.add("onTableAction", (value, value1, value2) => {
+ cy.get(commonlocators.dropdownSelectButton)
+ .eq(value)
+ .click();
+ cy.get(commonlocators.chooseAction)
+ .children()
+ .contains("Show Message")
+ .click();
+ cy.testJsontext(value1, value2);
+});
+
+Cypress.Commands.add("selectShowMsg", () => {
cy.get(commonlocators.chooseAction)
.children()
.contains("Show Message")
@@ -2036,6 +2052,32 @@ Cypress.Commands.add(
},
);
+Cypress.Commands.add(
+ "fillMySQLDatasourceForm",
+ (shouldAddTrailingSpaces = false) => {
+ const hostAddress = shouldAddTrailingSpaces
+ ? datasourceFormData["mysql-host"] + " "
+ : datasourceFormData["mysql-host"];
+ const databaseName = shouldAddTrailingSpaces
+ ? datasourceFormData["mysql-databaseName"] + " "
+ : datasourceFormData["mysql-databaseName"];
+
+ cy.get(datasourceEditor.host).type(hostAddress);
+ cy.get(datasourceEditor.port).type(datasourceFormData["mysql-port"]);
+ cy.get(datasourceEditor.databaseName)
+ .clear()
+ .type(databaseName);
+
+ cy.get(datasourceEditor.sectionAuthentication).click();
+ cy.get(datasourceEditor.username).type(
+ datasourceFormData["mysql-username"],
+ );
+ cy.get(datasourceEditor.password).type(
+ datasourceFormData["mysql-password"],
+ );
+ },
+);
+
Cypress.Commands.add(
"fillUsersMockDatasourceForm",
(shouldAddTrailingSpaces = false) => {
@@ -2164,6 +2206,21 @@ Cypress.Commands.add("dragAndDropToCanvas", (widgetType, { x, y }) => {
.trigger("mouseup", x, y, { eventConstructor: "MouseEvent" });
});
+Cypress.Commands.add(
+ "dragAndDropToWidget",
+ (widgetType, destinationWidget, { x, y }) => {
+ const selector = `.t--widget-card-draggable-${widgetType}`;
+ cy.get(selector)
+ .trigger("dragstart", { force: true })
+ .trigger("mousemove", x, y, { force: true });
+ const selector2 = `.t--draggable-${destinationWidget}`;
+ cy.get(selector2)
+ .trigger("mousemove", x, y, { eventConstructor: "MouseEvent" })
+ .trigger("mousemove", x, y, { eventConstructor: "MouseEvent" })
+ .trigger("mouseup", x, y, { eventConstructor: "MouseEvent" });
+ },
+);
+
Cypress.Commands.add("executeDbQuery", (queryName) => {
cy.get(widgetsPage.buttonOnClick)
.get(commonlocators.dropdownSelectButton)
@@ -2262,10 +2319,9 @@ Cypress.Commands.add("onClickActions", (forSuccess, forFailure) => {
.click()
.type(forSuccess)
.get("button.t--open-dropdown-Select-type")
- .click()
- .get("a.single-select div")
- .contains(forSuccess)
- .click();
+ .first()
+ .click({ force: true })
+ .selectOnClickOption(forSuccess);
cy.wait(2000);
// For Failure
@@ -2281,10 +2337,8 @@ Cypress.Commands.add("onClickActions", (forSuccess, forFailure) => {
.type(forFailure)
.get("button.t--open-dropdown-Select-type")
.last()
- .click()
- .get("a.single-select div")
- .contains(forFailure)
- .click();
+ .click({ force: true })
+ .selectOnClickOption(forFailure);
});
Cypress.Commands.add("copyWidget", (widget, widgetLocator) => {
@@ -2386,14 +2440,14 @@ Cypress.Commands.add("readTabledata", (rowNum, colNum) => {
});
Cypress.Commands.add("getDate", (date, dateFormate) => {
- const eDate = Cypress.moment()
+ const eDate = dayjs()
.add(date, "days")
.format(dateFormate);
return eDate;
});
Cypress.Commands.add("setDate", (date, dateFormate) => {
- const expDate = Cypress.moment()
+ const expDate = dayjs()
.add(date, "days")
.format(dateFormate);
const sel = `.DayPicker-Day[aria-label=\"${expDate}\"]`;
@@ -2464,6 +2518,9 @@ Cypress.Commands.add("startServerAndRoutes", () => {
cy.route("GET", "/api/v1/datasources/*/structure?ignoreCache=*").as(
"getDatasourceStructure",
);
+ cy.route("PUT", "/api/v1/datasources/datasource-query/*").as(
+ "datasourceQuery",
+ );
cy.route("PUT", "/api/v1/pages/crud-page/*").as("replaceLayoutWithCRUDPage");
cy.route("POST", "/api/v1/pages/crud-page").as("generateCRUDPage");
@@ -2683,3 +2740,19 @@ Cypress.Commands.add("renameDatasource", (datasourceName) => {
Cypress.Commands.add("skipGenerateCRUDPage", () => {
cy.get(generatePage.buildFromScratchActionCard).click();
});
+
+Cypress.Commands.add("fillAmazonS3DatasourceForm", () => {
+ cy.get(datasourceEditor.projectID).type(Cypress.env("S3_ACCESS_KEY"));
+ cy.get(datasourceEditor.serviceAccCredential)
+ .clear()
+ .type(Cypress.env("S3_SECRET_KEY"));
+});
+
+Cypress.Commands.add("createAmazonS3Datasource", () => {
+ cy.NavigateToDatasourceEditor();
+ cy.get(datasourceEditor.AmazonS3).click();
+
+ cy.fillAmazonS3DatasourceForm();
+
+ cy.testSaveDatasource();
+});
diff --git a/app/client/gitbook-algolia-lambda.js b/app/client/gitbook-algolia-lambda.js
index 993644ee0982..b64502aa345a 100644
--- a/app/client/gitbook-algolia-lambda.js
+++ b/app/client/gitbook-algolia-lambda.js
@@ -1,8 +1,27 @@
-var https = require("https");
-const algoliasearch = require('algoliasearch');
+const https = require("https");
+const algoliasearch = require("algoliasearch");
+const aws = require("aws-sdk");
-const client = algoliasearch('AZ2Z9CJSJ0', 'e92300d4e8dbaf2cbaa9ebbbeb4e06e6');
-const index = client.initIndex('test_appsmith');
+const SSM = new aws.SSM();
+
+const DOCS_VERSION = "v1.2.1";
+
+const orderArr = [{
+ path: "master/core-concepts/building-the-ui",
+ order: 0,
+}, {
+ path: "master/core-concepts/connecting-to-databases",
+ order: 1
+}, {
+ path: "master/core-concepts/apis",
+ order: 2
+}, {
+ path: "master/core-concepts/connecting-ui-and-logic",
+ order: 3
+}, {
+ path: "master/core-concepts/building-the-ui/calling-apis-from-widgets#sending-data-to-apis-queries",
+ order: 4
+}];
var options = {
headers: {
@@ -17,7 +36,9 @@ console.log("Loading function");
function getPage(pageId) {
return new Promise((resolve, reject) => {
- https.get(`https://api-beta.gitbook.com/v1/spaces/-Lzuzdhj8LjrQPaeyCxr/content/v/master/id/${pageId}?format=markdown`, options, (res) => {
+ const url = `https://api-beta.gitbook.com/v1/spaces/-Lzuzdhj8LjrQPaeyCxr/content/v/${DOCS_VERSION}/id/${pageId}?format=markdown`;
+ console.log("Getting URL", url);
+ https.get(url, options, (res) => {
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
@@ -26,21 +47,22 @@ function getPage(pageId) {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
} catch (e) {
- reject(e)
console.error(e.message);
+ console.error("Full response body:", rawData);
+ reject(e);
}
});
- })
+ });
});
}
-const pages = []
+const pages = [];
function pushChildPages(masterPage) {
if (masterPage.pages) {
masterPage.pages.forEach(page => {
- page.path = masterPage.path + "/" + page.path;
+ page.path = (masterPage.path || masterPage.ref) + "/" + page.path;
pushChildPages(page);
page.pages = undefined;
pages.push(page);
@@ -48,95 +70,121 @@ function pushChildPages(masterPage) {
}
}
-const orderArr = [{
- path: "master/quick-start",
- order: 0,
-}, {
- path: "master/core-concepts/building-the-ui",
- order: 1
-}, {
- path: "master/core-concepts/building-the-ui/displaying-api-data",
- order: 2
-}, {
- path: "master/core-concepts/apis",
- order: 3
-}, {
- path: "master/core-concepts/apis/taking-inputs-from-widgets",
- order: 4
-}]
-
function swap(arr, index1, index2) {
- let x = arr[index1]
- arr[index1] = arr[index2]
- arr[index2] = x;
+ let x = arr[index1];
+ arr[index1] = arr[index2];
+ arr[index2] = x;
}
-exports.handler = async (event) => {
- const response = await new Promise((resolve, reject) => {
- const req = https.get("https://api-beta.gitbook.com/v1/spaces/-Lzuzdhj8LjrQPaeyCxr/content", options, (res) => {
+exports.handler = async (event, context, callback) => {
+ const parameters = await loadParametersFromStore("/" + process.env.ENV + "/algolia");
+ console.log('Received event:', JSON.stringify(event, null, 2));
+
+ const client = algoliasearch(parameters.application_id, parameters.api_key);
+ const algoliaIndex = client.initIndex("test_appsmith");
+
+ return await new Promise((resolve, reject) => {
+ https.get("https://api-beta.gitbook.com/v1/spaces/-Lzuzdhj8LjrQPaeyCxr/content", options, (res) => {
+ console.log("Setting up response handlers for GitBook API request");
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
- let masterPage = parsedData.variants[0].page;
+ let requiredIndex = parsedData.variants.findIndex(varaint => varaint.uid === DOCS_VERSION);
+ let masterPage = parsedData.variants[requiredIndex].page;
pushChildPages(masterPage);
- masterPage.pages = undefined;
+ delete masterPage.pages;
pages.push(masterPage);
let promises = pages.map(page => page.uid).map(getPage);
+
Promise.all(promises).then(updatedPages => {
+
updatedPages.forEach((page, index) => {
page.path = pages[index].path;
- page.pages = undefined;
+ delete page.pages;
page.objectID = page.uid;
- if (page.path === "master/changelog") {
- page.document = undefined
+ delete page.uid;
+ if(page.path.endsWith("/changelog")) {
+ delete page.document;
}
});
orderArr.forEach(order => {
- let index = updatedPages.findIndex(i => i.path === order.path)
- if (index !== -1) {
- swap(updatedPages, index, order.order)
- }
- })
+ let index = updatedPages.findIndex(i => i.path === order.path);
+ if(index !== -1) {
+ swap(updatedPages, index, order.order);
+ }
+ });
+
+ updatedPages = updatedPages.map((item, index) => {return {...item, defaultOrder: index}});
- updatedPages = updatedPages.map((item, index) => { return { ...item, defaultOrder: index } })
+ // Truncate large docs.
+ updatedPages.filter(page => page.document).forEach(page => {
+ const size = JSON.stringify(page).length;
+ if (size < 10000) {
+ return;
+ }
+ console.log("Truncating page", page);
+ page.document = page.document.substr(0, page.document.length - (JSON.stringify(page).length - 9900));
+ });//*/
+ console.log("Pages:", updatedPages.map(page => ({objectID: page.objectID, size: JSON.stringify(page).length, title: page.title, path: page.path})));
// resolve({
// statusCode: 200,
// body: JSON.stringify(updatedPages)
// })
- index.replaceAllObjects(updatedPages, {
+ algoliaIndex.replaceAllObjects(updatedPages, {
autoGenerateObjectIDIfNotExist: true
}).then(({ objectIDs }) => {
- console.log(objectIDs);
+ console.log("Algolia upload finished", objectIDs);
+ callback(null, 'Finished');
resolve({
statusCode: 200,
body: JSON.stringify(updatedPages)
- })
+ });
}).catch(e => {
+ console.error("Algolia upload failed", e);
reject({
statusCode: 500,
body: 'Algolia upload failed.'
- })
- })
+ });
+ });
});
} catch (e) {
reject({
- statusCode: 500,
- body: 'Most probably gitbook getPage apis failed'
- })
+ statusCode: 500,
+ body: 'Most probably gitbook getPage apis failed'
+ });
}
});
+ }).on("error", (e) => {
+ console.error(e);
+ reject({
+ statusCode: 500,
+ body: "Error executing GitBook API request",
+ error: e
+ });
});
});
- return response;
};
+
+async function loadParametersFromStore(prefix) {
+ const parametersResponse = await SSM.getParametersByPath({Path: prefix, WithDecryption: true}).promise();
+ console.log("parametersResponse", parametersResponse);
+
+ const parameters = {};
+ for (const paramObject of parametersResponse.Parameters) {
+ parameters[paramObject.Name.replace(prefix + "/", "")] = paramObject.Value;
+ }
+
+ console.log("Parameters", parameters);
+ return parameters;
+}
diff --git a/app/client/package.json b/app/client/package.json
index a51cb24804e1..c8771c946a3b 100644
--- a/app/client/package.json
+++ b/app/client/package.json
@@ -61,6 +61,7 @@
"core-js": "^3.9.1",
"craco-alias": "^2.1.1",
"cypress-log-to-output": "^1.1.2",
+ "dayjs": "^1.10.6",
"deep-diff": "^1.0.2",
"downloadjs": "^1.4.7",
"draft-js": "^0.11.7",
@@ -146,7 +147,7 @@
"styled-components": "^5.2.0",
"styled-system": "^5.1.5",
"tern": "^0.21.0",
- "tinycolor2": "^1.4.1",
+ "tinycolor2": "^1.4.2",
"toposort": "^2.0.2",
"ts-loader": "^6.0.4",
"tslib": "^2.1.0",
@@ -157,8 +158,8 @@
"zipcelx": "^1.6.2"
},
"scripts": {
- "analyze": "source-map-explorer 'build/static/js/*.js'",
- "start": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_CLIENT_LOG_LEVEL=debug HOST=dev.appsmith.com SKIP_PREFLIGHT_CHECK=true craco start",
+ "analyze": "yarn cra-bundle-analyzer",
+ "start": "BROWSER=none EXTEND_ESLINT=true REACT_APP_ENVIRONMENT=DEVELOPMENT REACT_APP_CLIENT_LOG_LEVEL=debug HOST=dev.appsmith.com craco start",
"build": "./build.sh",
"build-local": "craco --max-old-space-size=4096 build --config craco.build.config.js",
"build-staging": "REACT_APP_ENVIRONMENT=STAGING craco --max-old-space-size=4096 build --config craco.build.config.js",
@@ -234,8 +235,9 @@
"@typescript-eslint/parser": "^4.15.0",
"babel-loader": "^8.1.0",
"babel-plugin-styled-components": "^1.10.7",
+ "cra-bundle-analyzer": "^0.1.0",
"craco-babel-loader": "^0.1.4",
- "cypress": "6.2.1",
+ "cypress": "7.6.0",
"cypress-file-upload": "^4.1.1",
"cypress-multi-reporters": "^1.2.4",
"cypress-xpath": "^1.4.0",
diff --git a/app/client/src/actions/canvasSelectionActions.ts b/app/client/src/actions/canvasSelectionActions.ts
index 8f59ecc967b7..1c14985c0e20 100644
--- a/app/client/src/actions/canvasSelectionActions.ts
+++ b/app/client/src/actions/canvasSelectionActions.ts
@@ -1,5 +1,20 @@
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import { SelectedArenaDimensions } from "pages/common/CanvasSelectionArena";
+import { XYCord } from "utils/hooks/useCanvasDragging";
+
+export const setCanvasSelectionFromEditor = (
+ start: boolean,
+ startPoints?: XYCord,
+) => {
+ return {
+ type: start
+ ? ReduxActionTypes.START_CANVAS_SELECTION_FROM_EDITOR
+ : ReduxActionTypes.STOP_CANVAS_SELECTION_FROM_EDITOR,
+ payload: {
+ ...(start && startPoints ? { startPoints } : {}),
+ },
+ };
+};
export const setCanvasSelectionStateAction = (
start: boolean,
diff --git a/app/client/src/actions/crudInfoModalActions.ts b/app/client/src/actions/crudInfoModalActions.ts
index 2dd88b7874e5..484507a74e24 100644
--- a/app/client/src/actions/crudInfoModalActions.ts
+++ b/app/client/src/actions/crudInfoModalActions.ts
@@ -1,6 +1,14 @@
import { ReduxActionTypes } from "constants/ReduxActionConstants";
-export const setCrudInfoModalOpen = (payload: boolean) => {
+export type SetCrudInfoModalOpenPayload = {
+ open: boolean;
+ generateCRUDSuccessInfo?: {
+ successImageUrl: string;
+ successMessage: string;
+ };
+};
+
+export const setCrudInfoModalData = (payload: SetCrudInfoModalOpenPayload) => {
return {
type: ReduxActionTypes.SET_CRUD_INFO_MODAL_OPEN,
payload,
diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts
index 131a626ec47d..85d63beb2146 100644
--- a/app/client/src/actions/datasourceActions.ts
+++ b/app/client/src/actions/datasourceActions.ts
@@ -199,20 +199,20 @@ export const getOAuthAccessToken = (datasourceId: string) => {
};
};
-export type executeDatasourceQuerySuccessPayload = {
+export type executeDatasourceQuerySuccessPayload = {
responseMeta: ResponseMeta;
data: {
- body: Array<{ id: string; name: string }>;
+ body: T;
headers: Record;
statusCode: string;
isExecutionSuccess: boolean;
};
};
-type errorPayload = unknown;
+type errorPayload = string;
-export type executeDatasourceQueryReduxAction = ReduxActionWithCallbacks<
+export type executeDatasourceQueryReduxAction = ReduxActionWithCallbacks<
executeDatasourceQueryRequest,
- executeDatasourceQuerySuccessPayload,
+ executeDatasourceQuerySuccessPayload,
errorPayload
>;
@@ -222,9 +222,11 @@ export const executeDatasourceQuery = ({
payload,
}: {
onErrorCallback?: (payload: errorPayload) => void;
- onSuccessCallback?: (payload: executeDatasourceQuerySuccessPayload) => void;
+ onSuccessCallback?: (
+ payload: executeDatasourceQuerySuccessPayload,
+ ) => void;
payload: executeDatasourceQueryRequest;
-}): executeDatasourceQueryReduxAction => {
+}): executeDatasourceQueryReduxAction => {
return {
type: ReduxActionTypes.EXECUTE_DATASOURCE_QUERY_INIT,
payload,
diff --git a/app/client/src/actions/debuggerActions.ts b/app/client/src/actions/debuggerActions.ts
index d78b600239c4..0ca69c4ac099 100644
--- a/app/client/src/actions/debuggerActions.ts
+++ b/app/client/src/actions/debuggerActions.ts
@@ -11,6 +11,7 @@ export interface LogDebuggerErrorAnalyticsPayload {
errorMessages?: Message[];
errorMessage?: Message["message"];
errorType?: Message["type"];
+ errorSubType?: Message["subType"];
analytics?: Log["analytics"];
}
@@ -67,3 +68,8 @@ export const logDebuggerErrorAnalytics = (
type: ReduxActionTypes.DEBUGGER_ERROR_ANALYTICS,
payload,
});
+
+export const hideDebuggerErrors = (payload: boolean) => ({
+ type: ReduxActionTypes.HIDE_DEBUGGER_ERRORS,
+ payload,
+});
diff --git a/app/client/src/actions/evaluationActions.ts b/app/client/src/actions/evaluationActions.ts
index c6111d88cc46..7db95a2fe253 100644
--- a/app/client/src/actions/evaluationActions.ts
+++ b/app/client/src/actions/evaluationActions.ts
@@ -7,6 +7,7 @@ import _ from "lodash";
import { DataTree } from "../entities/DataTree/dataTreeFactory";
import { DependencyMap } from "../utils/DynamicBindingUtils";
import { Diff } from "deep-diff";
+import { QueryActionConfig } from "../entities/Action";
export const FIRST_EVAL_REDUX_ACTIONS = [
// Pages
@@ -80,3 +81,26 @@ export const setDependencyMap = (
payload: { inverseDependencyMap },
};
};
+
+// Called when a form is being setup, for setting up the base condition evaluations for the form
+export const initFormEvaluations = (
+ editorConfig: any,
+ settingConfig: any,
+ formId: string,
+) => {
+ return {
+ type: ReduxActionTypes.INIT_FORM_EVALUATION,
+ payload: { editorConfig, settingConfig, formId },
+ };
+};
+
+// Called when there is change in the data of the form, re evaluates the whole form
+export const startFormEvaluations = (
+ formId: string,
+ formData: QueryActionConfig,
+) => {
+ return {
+ type: ReduxActionTypes.RUN_FORM_EVALUATION,
+ payload: { formId, actionConfiguration: formData },
+ };
+};
diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx
index bac1de361d54..0505709eca82 100644
--- a/app/client/src/actions/pageActions.tsx
+++ b/app/client/src/actions/pageActions.tsx
@@ -319,25 +319,20 @@ export interface ReduxActionWithExtraParams extends ReduxAction {
extraParams: Record;
}
-export const generateTemplateSuccess = ({
- isNewPage,
- layoutId,
- pageId,
- pageName,
-}: {
- layoutId: string;
- pageId: string;
- pageName: string;
+export type GenerateCRUDSuccess = {
+ page: {
+ layouts: Array;
+ id: string;
+ name: string;
+ isDefault?: boolean;
+ };
isNewPage: boolean;
-}) => {
+};
+
+export const generateTemplateSuccess = (payload: GenerateCRUDSuccess) => {
return {
type: ReduxActionTypes.GENERATE_TEMPLATE_PAGE_SUCCESS,
- payload: {
- layoutId,
- pageId,
- pageName,
- isNewPage,
- },
+ payload,
};
};
@@ -353,6 +348,7 @@ export const generateTemplateToUpdatePage = ({
datasourceId,
mode,
pageId,
+ pluginSpecificParams,
searchColumn,
tableName,
}: GenerateTemplatePageRequest): ReduxActionWithExtraParams => {
@@ -365,6 +361,7 @@ export const generateTemplateToUpdatePage = ({
applicationId,
columns,
searchColumn,
+ pluginSpecificParams,
},
extraParams: {
mode,
diff --git a/app/client/src/actions/widgetActions.tsx b/app/client/src/actions/widgetActions.tsx
index fc064d25da81..822f0deaf798 100644
--- a/app/client/src/actions/widgetActions.tsx
+++ b/app/client/src/actions/widgetActions.tsx
@@ -121,9 +121,12 @@ export const copyWidget = (isShortcut: boolean) => {
};
};
-export const pasteWidget = () => {
+export const pasteWidget = (groupWidgets = false) => {
return {
type: ReduxActionTypes.PASTE_COPIED_WIDGET_INIT,
+ payload: {
+ groupWidgets: groupWidgets,
+ },
};
};
@@ -152,3 +155,15 @@ export const addSuggestedWidget = (payload: Partial) => {
payload,
};
};
+
+/**
+ * action to group selected widgets into container
+ *
+ * @param queryName
+ * @returns
+ */
+export const groupWidgets = () => {
+ return {
+ type: ReduxActionTypes.GROUP_WIDGETS_INIT,
+ };
+};
diff --git a/app/client/src/actions/widgetSelectionActions.ts b/app/client/src/actions/widgetSelectionActions.ts
index bca16b91ea11..c48239016545 100644
--- a/app/client/src/actions/widgetSelectionActions.ts
+++ b/app/client/src/actions/widgetSelectionActions.ts
@@ -1,5 +1,4 @@
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
-import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
export const selectWidgetAction = (
widgetId?: string,
@@ -50,14 +49,9 @@ export const deselectMultipleWidgetsAction = (
};
};
-export const selectAllWidgetsInCanvasInitAction = (
- canvasId = MAIN_CONTAINER_WIDGET_ID,
-): ReduxAction<{ canvasId: string }> => {
+export const selectAllWidgetsInCanvasInitAction = () => {
return {
type: ReduxActionTypes.SELECT_ALL_WIDGETS_IN_CANVAS_INIT,
- payload: {
- canvasId,
- },
};
};
diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx
index cf0ed0c2e126..eaddf7cf4d12 100644
--- a/app/client/src/api/ActionAPI.tsx
+++ b/app/client/src/api/ActionAPI.tsx
@@ -69,6 +69,7 @@ export interface ActionExecutionResponse {
statusCode: string;
isExecutionSuccess: boolean;
request: ActionApiResponseReq;
+ errorType?: string;
};
clientMeta: {
duration: string;
diff --git a/app/client/src/api/DatasourcesApi.ts b/app/client/src/api/DatasourcesApi.ts
index 9e625bc1ee13..0993c5bbb8af 100644
--- a/app/client/src/api/DatasourcesApi.ts
+++ b/app/client/src/api/DatasourcesApi.ts
@@ -25,7 +25,7 @@ export interface EmbeddedRestDatasourceRequest {
pluginId: string;
}
-type executeQueryData = Array<{ key: string; value?: string }>;
+type executeQueryData = Array<{ key?: string; value?: string }>;
export interface executeDatasourceQueryRequest {
datasourceId: string;
diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx
index b6fa34c78f84..cb143d75c10d 100644
--- a/app/client/src/api/PageApi.tsx
+++ b/app/client/src/api/PageApi.tsx
@@ -122,6 +122,7 @@ export interface GenerateTemplatePageRequest {
columns?: string[];
searchColumn?: string;
mode?: string;
+ pluginSpecificParams?: Record;
}
export type GenerateTemplatePageRequestResponse = ApiResponse & {
diff --git a/app/client/src/api/PluginApi.ts b/app/client/src/api/PluginApi.ts
index f28b4ff32adc..953f36c32adf 100644
--- a/app/client/src/api/PluginApi.ts
+++ b/app/client/src/api/PluginApi.ts
@@ -8,14 +8,25 @@ export type PluginId = string;
export type PluginPackageName = string;
export type GenerateCRUDEnabledPluginMap = Record;
+export enum UIComponentTypes {
+ DbEditorForm = "DbEditorForm",
+ UQIDbEditorForm = "UQIDbEditorForm",
+ ApiEditorForm = "ApiEditorForm",
+ RapidApiEditorForm = "RapidApiEditorForm",
+}
+
+export enum DatasourceComponentTypes {
+ RestAPIDatasourceForm = "RestAPIDatasourceForm",
+ AutoForm = "AutoForm",
+}
export interface Plugin {
id: string;
name: string;
type: PluginType;
packageName: string;
iconLocation?: string;
- uiComponent: "ApiEditorForm" | "RapidApiEditorForm" | "DbEditorForm";
- datasourceComponent: "RestAPIDatasourceForm" | "AutoForm";
+ uiComponent: UIComponentTypes;
+ datasourceComponent: DatasourceComponentTypes;
allowUserDatasources?: boolean;
templates: Record;
responseType?: "TABLE" | "JSON";
diff --git a/app/client/src/assets/icons/ads/arrow-left-1.svg b/app/client/src/assets/icons/ads/arrow-left-1.svg
new file mode 100644
index 000000000000..5cc8c171ee56
--- /dev/null
+++ b/app/client/src/assets/icons/ads/arrow-left-1.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/client/src/assets/icons/ads/book-open-line.svg b/app/client/src/assets/icons/ads/book-open-line.svg
new file mode 100644
index 000000000000..48fc856ae962
--- /dev/null
+++ b/app/client/src/assets/icons/ads/book-open-line.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/client/src/assets/icons/ads/git-merge.svg b/app/client/src/assets/icons/ads/git-merge.svg
new file mode 100644
index 000000000000..62d54cc1d7c8
--- /dev/null
+++ b/app/client/src/assets/icons/ads/git-merge.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/client/src/comments/CommentsShowcaseCarousel/CommentsCarouselModal.tsx b/app/client/src/comments/CommentsShowcaseCarousel/CommentsCarouselModal.tsx
index 2f361020d7fc..a36ed9f1e42e 100644
--- a/app/client/src/comments/CommentsShowcaseCarousel/CommentsCarouselModal.tsx
+++ b/app/client/src/comments/CommentsShowcaseCarousel/CommentsCarouselModal.tsx
@@ -3,6 +3,7 @@ import ModalComponent from "components/designSystems/blueprint/ModalComponent";
import { Layers } from "constants/Layers";
import { useDispatch } from "react-redux";
import { hideCommentsIntroCarousel } from "actions/commentActions";
+import AnalyticsUtil from "utils/AnalyticsUtil";
function ShowcaseCarouselModal({ children }: { children: React.ReactNode }) {
const dispatch = useDispatch();
@@ -18,6 +19,7 @@ function ShowcaseCarouselModal({ children }: { children: React.ReactNode }) {
left={25}
onClose={() => {
dispatch(hideCommentsIntroCarousel());
+ AnalyticsUtil.logEvent("COMMENTS_ONBOARDING_MODAL_DISMISSED");
}}
overlayClassName="comments-onboarding-carousel"
portalClassName="comments-onboarding-carousel-portal"
diff --git a/app/client/src/comments/CommentsShowcaseCarousel/index.tsx b/app/client/src/comments/CommentsShowcaseCarousel/index.tsx
index f8627f2bf4bd..54e22b136b1f 100644
--- a/app/client/src/comments/CommentsShowcaseCarousel/index.tsx
+++ b/app/client/src/comments/CommentsShowcaseCarousel/index.tsx
@@ -34,6 +34,7 @@ import stepOneThumbnail from "assets/images/comments-onboarding/thumbnails/step-
import stepTwoThumbnail from "assets/images/comments-onboarding/thumbnails/step-2.jpg";
import { setCommentModeInUrl } from "pages/Editor/ToggleModeButton";
+import AnalyticsUtil from "utils/AnalyticsUtil";
const getBanner = (step: number) =>
`${S3_BUCKET_URL}/comments/step-${step}.png`;
@@ -143,6 +144,13 @@ function IntroStep(props: {
const IntroStepThemed = withTheme(IntroStep);
+const handleStepChange = (currentStep: number, nextStep: number) => {
+ AnalyticsUtil.logEvent("COMMENTS_ONBOARDING_STEP_CHANGE", {
+ currentStep,
+ nextStep,
+ });
+};
+
const getSteps = (
isSubmitProfileFormDisabled: boolean,
finalSubmit: () => void,
@@ -221,6 +229,9 @@ export default function CommentsShowcaseCarousel() {
const [activeIndex, setActiveIndex] = useState(initialStep);
const finalSubmit = async () => {
+ AnalyticsUtil.logEvent("COMMENTS_ONBOARDING_SUBMIT_BUTTON_CLICK", {
+ skipped: isSkipped,
+ });
dispatch(hideCommentsIntroCarousel());
await setCommentsIntroSeen(true);
@@ -237,6 +248,7 @@ export default function CommentsShowcaseCarousel() {
};
const onSkip = () => {
+ AnalyticsUtil.logEvent("COMMENTS_ONBOARDING_SKIP_BUTTON_CLICK");
setActiveIndex(finalStep);
setIsSkipped(true);
};
@@ -262,6 +274,7 @@ export default function CommentsShowcaseCarousel() {
diff --git a/app/client/src/components/ads/Button.tsx b/app/client/src/components/ads/Button.tsx
index 431deab16832..fa996e18b919 100644
--- a/app/client/src/components/ads/Button.tsx
+++ b/app/client/src/components/ads/Button.tsx
@@ -293,6 +293,7 @@ const btnFontStyles = (props: ThemeProp & ButtonProps): BtnFontType => {
};
const ButtonStyles = css`
+ user-select: none;
width: ${(props) =>
props.width ? props.width : props.fill ? "100%" : "auto"};
height: ${(props) => btnFontStyles(props).height}px;
diff --git a/app/client/src/components/ads/Checkbox.tsx b/app/client/src/components/ads/Checkbox.tsx
index c9041b0aef98..6a90affe3b8a 100644
--- a/app/client/src/components/ads/Checkbox.tsx
+++ b/app/client/src/components/ads/Checkbox.tsx
@@ -28,13 +28,17 @@ const Checkmark = styled.span<{
? props.disabled
? props.theme.colors.checkbox.disabled
: props.backgroundColor || props.theme.colors.info.main
+ : props.disabled
+ ? props.theme.colors.checkbox.disabled
: "transparent"};
- border: 2px solid
+ border: 1.8px solid
${(props) =>
props.isChecked
? props.disabled
? props.theme.colors.checkbox.disabled
: props.backgroundColor || props.theme.colors.info.main
+ : props.disabled
+ ? props.theme.colors.checkbox.disabled
: props.theme.colors.checkbox.unchecked};
&::after {
diff --git a/app/client/src/components/ads/ColorPickerComponent.tsx b/app/client/src/components/ads/ColorPickerComponent.tsx
index c24d907c3e13..9b162736f9ff 100644
--- a/app/client/src/components/ads/ColorPickerComponent.tsx
+++ b/app/client/src/components/ads/ColorPickerComponent.tsx
@@ -151,6 +151,7 @@ interface ColorPickerProps {
function ColorPickerComponent(props: ColorPickerProps) {
const [color, setColor] = React.useState(props.color);
+ const [isOpen, setOpen] = React.useState(false);
const debouncedOnChange = React.useCallback(
debounce(props.changeColor, 500),
[props.changeColor],
@@ -164,6 +165,7 @@ function ColorPickerComponent(props: ColorPickerProps) {
+ setOpen(true)} />
) : (
-
+ setOpen(true)}>
)
}
+ onBlur={() => {
+ // Case 1
+ // On Tab key press Input loses focus and onKeyUp handler is not called
+ // To handle Tab key press onBlur event is used instead
+ // As input will lose focus on Tab press
+ // Case 2
+ // if user clicks on ColorBoard blur is called first and color is not updated
+ // to prevent that make sure to wait for color update before onBlur
+ setTimeout(() => {
+ setOpen(false);
+ }, 100);
+ }}
onChange={handleChangeColor}
+ onFocus={() => setOpen(true)}
+ onKeyUp={(e) => {
+ if (e.key === "Enter") {
+ setOpen((state) => !state);
+ }
+ }}
placeholder="enter color name or hex"
value={color}
/>
{
setColor(color);
+ setOpen(false);
props.changeColor(color);
}}
selectedColor={color}
diff --git a/app/client/src/components/ads/CurrencyCodeDropdown.tsx b/app/client/src/components/ads/CurrencyCodeDropdown.tsx
new file mode 100644
index 000000000000..bce5d6b934bb
--- /dev/null
+++ b/app/client/src/components/ads/CurrencyCodeDropdown.tsx
@@ -0,0 +1,108 @@
+import React from "react";
+import styled from "styled-components";
+import Dropdown, { DropdownOption } from "components/ads/Dropdown";
+import { CurrencyTypeOptions, CurrencyOptionProps } from "constants/Currency";
+import Icon, { IconSize } from "components/ads/Icon";
+import { countryToFlag } from "components/designSystems/blueprint/InputComponent/utilties";
+
+const DropdownTriggerIconWrapper = styled.div`
+ height: 19px;
+ padding: 9px 5px 9px 12px;
+ width: 40px;
+ height: 19px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ line-height: 19px;
+ letter-spacing: -0.24px;
+ color: #090707;
+`;
+
+const CurrencyIconWrapper = styled.span`
+ height: 100%;
+ padding: 6px 4px 6px 12px;
+ width: 28px;
+ position: absolute;
+ left: 0;
+ z-index: 16;
+ font-size: 14px;
+ line-height: 19px;
+ letter-spacing: -0.24px;
+ color: #090707;
+`;
+
+const getCurrencyOptions = (): Array => {
+ return CurrencyTypeOptions.map((item: CurrencyOptionProps) => {
+ return {
+ leftElement: countryToFlag(item.code),
+ searchText: item.label,
+ label: `${item.currency} - ${item.currency_name}`,
+ value: item.code,
+ id: item.symbol_native,
+ };
+ });
+};
+
+export const CurrencyDropdownOptions = getCurrencyOptions();
+
+export const getSelectedCurrency = (
+ currencyCountryCode?: string,
+): DropdownOption => {
+ let selectedCurrency: CurrencyOptionProps | undefined = currencyCountryCode
+ ? CurrencyTypeOptions.find((item: CurrencyOptionProps) => {
+ return item.code === currencyCountryCode;
+ })
+ : undefined;
+ if (!selectedCurrency) {
+ selectedCurrency = {
+ code: "US",
+ currency: "USD",
+ currency_name: "US Dollar",
+ label: "United States",
+ phone: "1",
+ symbol_native: "$",
+ };
+ }
+ return {
+ label: `${selectedCurrency.currency} - ${selectedCurrency.currency_name}`,
+ searchText: selectedCurrency.label,
+ value: selectedCurrency.code,
+ id: selectedCurrency.symbol_native,
+ };
+};
+
+interface CurrencyDropdownProps {
+ onCurrencyTypeChange: (currencyCountryCode?: string) => void;
+ options: Array;
+ selected: DropdownOption;
+ allowCurrencyChange?: boolean;
+}
+
+export default function CurrencyTypeDropdown(props: CurrencyDropdownProps) {
+ const selectedCurrency = getSelectedCurrency(props.selected.value).id;
+ if (!props.allowCurrencyChange) {
+ return {selectedCurrency};
+ }
+ const dropdownTriggerIcon = (
+
+ {selectedCurrency}
+
+
+ );
+ return (
+
+ );
+}
diff --git a/app/client/src/components/ads/Dropdown.tsx b/app/client/src/components/ads/Dropdown.tsx
index 8043c1eff819..ee887f1c492b 100644
--- a/app/client/src/components/ads/Dropdown.tsx
+++ b/app/client/src/components/ads/Dropdown.tsx
@@ -84,7 +84,7 @@ export interface RenderDropdownOptionType {
optionClickHandler?: (dropdownOption: DropdownOption) => void;
}
-export const DropdownContainer = styled.div<{ width: string; height: string }>`
+export const DropdownContainer = styled.div<{ width: string; height?: string }>`
width: ${(props) => props.width};
height: ${(props) => props.height};
position: relative;
@@ -93,7 +93,6 @@ export const DropdownContainer = styled.div<{ width: string; height: string }>`
const DropdownTriggerWrapper = styled.div<{
isOpen: boolean;
disabled?: boolean;
- height: string;
}>`
height: 100%;
display: flex;
@@ -117,6 +116,7 @@ const Selected = styled.div<{
bgColor?: string;
hasError?: boolean;
selected?: boolean;
+ isLoading?: boolean;
}>`
padding: ${(props) => props.theme.spaces[2]}px
${(props) => props.theme.spaces[3]}px;
@@ -138,7 +138,8 @@ const Selected = styled.div<{
justify-content: space-between;
width: 100%;
height: ${(props) => props.height};
- cursor: pointer;
+ cursor: ${(props) =>
+ props.disabled || props.isLoading ? "not-allowed" : "pointer"};
${(props) =>
props.hasError
? `.sub-text {
@@ -164,6 +165,10 @@ const Selected = styled.div<{
? "box-shadow: 0px 0px 4px 4px rgba(203, 72, 16, 0.18)"
: null};
.${Classes.TEXT} {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ width: calc(100% - 10px);
${(props) =>
props.disabled
? `color: ${props.theme.colors.dropdown.header.disabledText}`
@@ -189,7 +194,7 @@ export const DropdownWrapper = styled.div<{
width: string;
}>`
width: ${(props) => props.width};
- height: 32px;
+ height: fit-content;
z-index: 1;
background-color: ${(props) => props.theme.colors.dropdown.menuBg};
box-shadow: ${(props) => props.theme.colors.dropdown.menuShadow};
@@ -316,6 +321,15 @@ const HeaderWrapper = styled.div`
const SelectedDropDownHolder = styled.div`
display: flex;
align-items: center;
+ min-width: 0;
+ width: 100%;
+ overflow: hidden;
+
+ & ${Text} {
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
`;
const SelectedIcon = styled(Icon)`
@@ -363,7 +377,7 @@ const DropdownIcon = styled(Icon)`
const ErrorMsg = styled.span`
${(props) => getTypographyByKey(props, "p3")};
color: ${Colors.POMEGRANATE2};
- margin: 6px 0px 10px;
+ margin-top: ${(props) => props.theme.spaces[3]}px;
`;
const HelperMsg = styled.span`
@@ -457,8 +471,11 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
setOptions(filteredOptions);
onSearch && onSearch(searchStr);
};
- return (
-
+ return options.length > 0 ? (
+
{props.enableSearch && (
- );
+ ) : null;
}
export default function Dropdown(props: DropdownProps) {
@@ -537,8 +554,15 @@ export default function Dropdown(props: DropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const [selected, setSelected] = useState(props.selected);
+ const closeIfOpen = () => {
+ if (isOpen) {
+ setIsOpen(false);
+ }
+ };
+
useEffect(() => {
setSelected(props.selected);
+ closeIfOpen();
}, [props.selected]);
const optionClickHandler = useCallback(
@@ -551,14 +575,20 @@ export default function Dropdown(props: DropdownProps) {
[onSelect],
);
- const disabled = props.disabled || isLoading || !!errorMsg;
- const downIconColor = errorMsg ? Colors.POMEGRANATE2 : "";
+ const disabled = props.disabled || isLoading;
+ const downIconColor = errorMsg ? Colors.POMEGRANATE2 : Colors.DARK_GRAY;
+
+ const onClickHandler = () => {
+ if (!props.disabled) {
+ setIsOpen(!isOpen);
+ }
+ };
+
const dropdownTrigger = props.dropdownTriggerIcon ? (
setIsOpen(!isOpen)}
+ onClick={onClickHandler}
>
{props.dropdownTriggerIcon}
@@ -615,7 +645,7 @@ export default function Dropdown(props: DropdownProps) {
isOpen={isOpen && !disabled}
minimal
modifiers={{ arrow: { enabled: true } }}
- onInteraction={(state) => setIsOpen(state)}
+ onInteraction={(state) => !disabled && setIsOpen(state)}
popoverClassName={props.className}
position={Position.BOTTOM_LEFT}
>
diff --git a/app/client/src/components/ads/ISDCodeDropdown.tsx b/app/client/src/components/ads/ISDCodeDropdown.tsx
new file mode 100644
index 000000000000..68d2ef7d4339
--- /dev/null
+++ b/app/client/src/components/ads/ISDCodeDropdown.tsx
@@ -0,0 +1,108 @@
+import React from "react";
+import styled from "styled-components";
+import Dropdown, { DropdownOption } from "components/ads/Dropdown";
+import Icon, { IconSize } from "components/ads/Icon";
+import { countryToFlag } from "components/designSystems/blueprint/InputComponent/utilties";
+import { ISDCodeOptions, ISDCodeProps } from "constants/ISDCodes";
+
+const DropdownTriggerIconWrapper = styled.div<{ disabled?: boolean }>`
+ padding: 9px 0px 9px 12px;
+ width: 85px;
+ min-width: 85px;
+ opacity: ${(props) => props.disabled && "0.6"};
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 14px;
+ height: ${(props) => (props.disabled ? 32 : 19)}px;
+ line-height: ${(props) => (props.disabled ? 32 : 19)}px;
+ letter-spacing: -0.24px;
+ color: #090707;
+ position: ${(props) => props.disabled && "absolute"};
+ .code {
+ margin-right: 4px;
+ pointer-events: none;
+ }
+ .icon-dropdown {
+ display: flex;
+ width: 30px;
+ justify-content: space-between;
+ }
+`;
+
+const getISDCodeOptions = (): Array => {
+ return ISDCodeOptions.map((item: ISDCodeProps) => {
+ return {
+ leftElement: countryToFlag(item.code),
+ searchText: item.name,
+ label: `${item.name} (${item.dial_code})`,
+ value: item.code,
+ id: item.dial_code,
+ };
+ });
+};
+
+export const ISDCodeDropdownOptions = getISDCodeOptions();
+
+export const getSelectedISDCode = (code?: string): DropdownOption => {
+ let selectedCountry: ISDCodeProps | undefined = code
+ ? ISDCodeOptions.find((item: ISDCodeProps) => {
+ return item.code === code;
+ })
+ : undefined;
+ if (!selectedCountry) {
+ selectedCountry = {
+ name: "United States",
+ dial_code: "+1",
+ code: "US",
+ };
+ }
+ return {
+ label: `${selectedCountry.name} (${selectedCountry.dial_code})`,
+ searchText: selectedCountry.name,
+ value: selectedCountry.code,
+ id: selectedCountry.dial_code,
+ };
+};
+
+interface ISDCodeDropdownProps {
+ onISDCodeChange: (code?: string) => void;
+ options: Array;
+ selected: DropdownOption;
+ allowCountryCodeChange?: boolean;
+ disabled: boolean;
+}
+
+export default function ISDCodeDropdown(props: ISDCodeDropdownProps) {
+ const selectedCountry = getSelectedISDCode(props.selected.value);
+ const dropdownTrigger = (
+
+
+ {selectedCountry.value && countryToFlag(selectedCountry.value)}
+
+
+ {selectedCountry.id && selectedCountry.id}
+
+ );
+ if (props.disabled) {
+ return dropdownTrigger;
+ }
+ return (
+
+ );
+}
diff --git a/app/client/src/components/ads/Icon.tsx b/app/client/src/components/ads/Icon.tsx
index 0c0e4035ece2..43d01d24bd94 100644
--- a/app/client/src/components/ads/Icon.tsx
+++ b/app/client/src/components/ads/Icon.tsx
@@ -1,6 +1,7 @@
import React, { forwardRef, Ref } from "react";
import { ReactComponent as DeleteIcon } from "assets/icons/ads/delete.svg";
import { ReactComponent as BookIcon } from "assets/icons/ads/book.svg";
+import { ReactComponent as BookLineIcon } from "assets/icons/ads/book-open-line.svg";
import { ReactComponent as BugIcon } from "assets/icons/ads/bug.svg";
import { ReactComponent as CancelIcon } from "assets/icons/ads/cancel.svg";
import { ReactComponent as ExpandMore } from "assets/icons/ads/expand-more.svg";
@@ -133,6 +134,7 @@ export const IconCollection = [
"upload",
"download",
"book",
+ "book-line",
"bug",
"cancel",
"cross",
@@ -273,6 +275,9 @@ const Icon = forwardRef(
case "book":
returnIcon = ;
break;
+ case "book-line":
+ returnIcon = ;
+ break;
case "bug":
returnIcon = ;
break;
diff --git a/app/client/src/components/ads/NumberedStep.tsx b/app/client/src/components/ads/NumberedStep.tsx
index 2f669a066f92..8c810822a180 100644
--- a/app/client/src/components/ads/NumberedStep.tsx
+++ b/app/client/src/components/ads/NumberedStep.tsx
@@ -12,7 +12,7 @@ const Container = styled.div`
`;
const Line = styled.div`
- width: 2px;
+ width: 1px;
flex: 1;
background-color: ${(props) => props.theme.colors.numberedStep.line};
`;
diff --git a/app/client/src/components/ads/ShowcaseCarousel.tsx b/app/client/src/components/ads/ShowcaseCarousel.tsx
index 97e4e1c7ce77..765b5022cb26 100644
--- a/app/client/src/components/ads/ShowcaseCarousel.tsx
+++ b/app/client/src/components/ads/ShowcaseCarousel.tsx
@@ -57,6 +57,7 @@ type Props = {
activeIndex: number;
setActiveIndex: (index: number) => void;
onClose: () => void;
+ onStepChange: (current: number, next: number) => void;
};
type DotsProps = {
@@ -84,6 +85,7 @@ export default function ShowcaseCarousel(props: Props) {
const [activeIndex, setCurrentIdxInState] = useState(props.activeIndex || 0);
const setCurrentIdx = (index: number) => {
+ if (activeIndex !== index) props.onStepChange(activeIndex, index);
setCurrentIdxInState(index);
props.setActiveIndex(index);
};
diff --git a/app/client/src/components/ads/Tabs.tsx b/app/client/src/components/ads/Tabs.tsx
index fd45629a257a..d8b45f73e19f 100644
--- a/app/client/src/components/ads/Tabs.tsx
+++ b/app/client/src/components/ads/Tabs.tsx
@@ -66,6 +66,10 @@ const TabsWrapper = styled.div<{
content: none;
}
}
+
+ .react-tabs__tab--selected {
+ background-color: transparent;
+ }
`;
export const TabTitle = styled.span`
@@ -74,6 +78,8 @@ export const TabTitle = styled.span`
line-height: ${(props) => props.theme.typography.h5.lineHeight - 3}px;
letter-spacing: ${(props) => props.theme.typography.h5.letterSpacing}px;
margin: 0;
+ display: flex;
+ align-items: center;
`;
export const TabCount = styled.div`
diff --git a/app/client/src/components/ads/TextInput.tsx b/app/client/src/components/ads/TextInput.tsx
index 52867efeed1b..441f073e05dd 100644
--- a/app/client/src/components/ads/TextInput.tsx
+++ b/app/client/src/components/ads/TextInput.tsx
@@ -42,6 +42,7 @@ export type TextInputProps = CommonComponentProps & {
placeholder?: string;
fill?: boolean;
defaultValue?: string;
+ value?: string;
validator?: (value: string) => { isValid: boolean; message: string };
onChange?: (value: string) => void;
readOnly?: boolean;
@@ -108,6 +109,7 @@ const StyledInput = styled((props) => {
>`
width: ${(props) => (props.fill ? "100%" : "260px")};
border-radius: 0;
+ caret-color: white;
outline: 0;
box-shadow: none;
border: 1.2px solid ${(props) => props.inputStyle.borderColor};
diff --git a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx
index 18f2537cff9f..6ecdbb2b3f5a 100644
--- a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx
+++ b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx
@@ -2,10 +2,14 @@ import _, { get } from "lodash";
import React from "react";
import styled from "styled-components";
-import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme";
-import { getAppsmithConfigs } from "configs";
-import { AllChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget";
import log from "loglevel";
+import { AllChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget";
+import { getAppsmithConfigs } from "configs";
+import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme";
+import {
+ LabelOrientation,
+ LABEL_ORIENTATION_COMPATIBLE_CHARTS,
+} from "constants/ChartConstants";
export interface CustomFusionChartConfig {
type: string;
@@ -48,16 +52,17 @@ FusionCharts.options.license({
});
export interface ChartComponentProps {
- chartType: ChartType;
+ allowHorizontalScroll: boolean;
chartData: AllChartData;
- customFusionChartConfig: CustomFusionChartConfig;
- xAxisName: string;
- yAxisName: string;
chartName: string;
- widgetId: string;
+ chartType: ChartType;
+ customFusionChartConfig: CustomFusionChartConfig;
isVisible?: boolean;
- allowHorizontalScroll: boolean;
+ labelOrientation?: LabelOrientation;
onDataPointClick: (selectedDataPoint: ChartSelectedDataPoint) => void;
+ widgetId: string;
+ xAxisName: string;
+ yAxisName: string;
}
const CanvasContainer = styled.div<
@@ -74,9 +79,14 @@ const CanvasContainer = styled.div<
padding: 10px 0 0 0;
}`;
+export const isLabelOrientationApplicableFor = (chartType: string) =>
+ LABEL_ORIENTATION_COMPATIBLE_CHARTS.includes(chartType);
+
class ChartComponent extends React.Component {
chartInstance = new FusionCharts();
+ chartContainerId = this.props.widgetId + "chart-container";
+
getChartType = () => {
const { allowHorizontalScroll, chartData, chartType } = this.props;
const dataLength = Object.keys(chartData).length;
@@ -151,7 +161,7 @@ class ChartComponent extends React.Component {
});
};
- getChartCategoriesMutliSeries = (chartData: AllChartData) => {
+ getChartCategoriesMultiSeries = (chartData: AllChartData) => {
const categories: string[] = [];
Object.keys(chartData).forEach((key: string) => {
@@ -173,7 +183,7 @@ class ChartComponent extends React.Component {
};
getChartCategories = (chartData: AllChartData) => {
- const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
+ const categories: string[] = this.getChartCategoriesMultiSeries(chartData);
if (categories.length === 0) {
return [
@@ -218,7 +228,7 @@ class ChartComponent extends React.Component {
* @returns
*/
getChartDataset = (chartData: AllChartData) => {
- const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
+ const categories: string[] = this.getChartCategoriesMultiSeries(chartData);
const dataset = Object.keys(chartData).map((key: string) => {
const item = get(chartData, `${key}`);
@@ -236,8 +246,32 @@ class ChartComponent extends React.Component {
return dataset;
};
+ getLabelOrientationConfig = () => {
+ switch (this.props.labelOrientation) {
+ case LabelOrientation.AUTO:
+ return {};
+ case LabelOrientation.ROTATE:
+ return {
+ labelDisplay: "rotate",
+ slantLabel: "0",
+ };
+ case LabelOrientation.SLANT:
+ return {
+ labelDisplay: "rotate",
+ slantLabel: "1",
+ };
+ case LabelOrientation.STAGGER:
+ return {
+ labelDisplay: "stagger",
+ };
+ default: {
+ return {};
+ }
+ }
+ };
+
getChartConfig = () => {
- return {
+ let config = {
caption: this.props.chartName,
xAxisName: this.props.xAxisName,
yAxisName: this.props.yAxisName,
@@ -246,6 +280,15 @@ class ChartComponent extends React.Component {
captionHorizontalPadding: 10,
alignCaptionWithCanvas: 0,
};
+
+ if (isLabelOrientationApplicableFor(this.props.chartType)) {
+ config = {
+ ...config,
+ ...this.getLabelOrientationConfig(),
+ };
+ }
+
+ return config;
};
getDatalength = () => {
@@ -338,7 +381,7 @@ class ChartComponent extends React.Component {
createGraph = () => {
if (this.props.chartType === "CUSTOM_FUSION_CHART") {
const chartConfig = {
- renderAt: this.props.widgetId + "chart-container",
+ renderAt: this.chartContainerId,
width: "100%",
height: "100%",
events: {
@@ -364,7 +407,7 @@ class ChartComponent extends React.Component {
const chartConfig = {
type: this.getChartType(),
- renderAt: this.props.widgetId + "chart-container",
+ renderAt: this.chartContainerId,
width: "100%",
height: "100%",
dataFormat: "json",
@@ -410,7 +453,7 @@ class ChartComponent extends React.Component {
if (!_.isEqual(prevProps, this.props)) {
if (this.props.chartType === "CUSTOM_FUSION_CHART") {
const chartConfig = {
- renderAt: this.props.widgetId + "chart-container",
+ renderAt: this.chartContainerId,
width: "100%",
height: "100%",
events: {
@@ -446,9 +489,7 @@ class ChartComponent extends React.Component {
render() {
//eslint-disable-next-line @typescript-eslint/no-unused-vars
const { onDataPointClick, ...rest } = this.props;
- return (
-
- );
+ return ;
}
}
diff --git a/app/client/src/components/designSystems/appsmith/FilePickerComponent.tsx b/app/client/src/components/designSystems/appsmith/FilePickerComponent.tsx
index c7732b0ff03d..011aa100be7e 100644
--- a/app/client/src/components/designSystems/appsmith/FilePickerComponent.tsx
+++ b/app/client/src/components/designSystems/appsmith/FilePickerComponent.tsx
@@ -29,9 +29,8 @@ class FilePickerComponent extends React.Component<
}
return (
{
+ if (!backgroundColor)
+ return prevButtonStyle
+ ? theme.colors.button[prevButtonStyle.toLowerCase()].solid.textColor
+ : theme.colors.button.custom.solid.dark.textColor;
+
+ const isDark = tinycolor(backgroundColor).isDark();
+ if (isDark) {
+ return theme.colors.button.custom.solid.light.textColor;
+ }
+ return theme.colors.button.custom.solid.dark.textColor;
+};
+
+const getCustomHoverColor = (
+ theme: Theme,
+ prevButtonStyle?: ButtonStyle,
+ buttonVariant?: ButtonVariant,
+ backgroundColor?: string,
+) => {
+ if (!backgroundColor) {
+ return prevButtonStyle
+ ? theme.colors.button[prevButtonStyle.toLowerCase()][
+ (buttonVariant || ButtonVariantTypes.SOLID).toLowerCase()
+ ].hoverColor
+ : tinycolor(theme.colors.button.primary.solid.textColor)
+ .darken()
+ .toString();
+ }
+ switch (buttonVariant) {
+ case ButtonVariantTypes.OUTLINE:
+ return backgroundColor
+ ? tinycolor(backgroundColor)
+ .lighten(40)
+ .toString()
+ : theme.colors.button.primary.outline.hoverColor;
+ break;
+ case ButtonVariantTypes.GHOST:
+ return backgroundColor
+ ? tinycolor(backgroundColor)
+ .lighten(40)
+ .toString()
+ : theme.colors.button.primary.ghost.hoverColor;
+ break;
+
+ default:
+ return backgroundColor
+ ? tinycolor(backgroundColor)
+ .darken(10)
+ .toString()
+ : theme.colors.button.primary.solid.hoverColor;
+ break;
+ }
+};
+
+const getCustomBackgroundColor = (
+ theme: Theme,
+ prevButtonStyle?: ButtonStyle,
+ buttonVariant?: ButtonVariant,
+ backgroundColor?: string,
+) => {
+ return buttonVariant === ButtonVariantTypes.SOLID
+ ? backgroundColor
+ ? backgroundColor
+ : prevButtonStyle
+ ? theme.colors.button[prevButtonStyle.toLowerCase()].solid.bgColor
+ : "none"
+ : "none";
+};
+
+const getCustomBorderColor = (
+ theme: Theme,
+ prevButtonStyle?: ButtonStyle,
+ buttonVariant?: ButtonVariant,
+ backgroundColor?: string,
+) => {
+ return buttonVariant === ButtonVariantTypes.OUTLINE
+ ? backgroundColor
+ ? backgroundColor
+ : prevButtonStyle
+ ? theme.colors.button[prevButtonStyle.toLowerCase()].outline.borderColor
+ : "none"
+ : "none";
+};
export const MenuButtonContainer = styled.div`
width: 100%;
@@ -17,46 +121,191 @@ export const MenuButtonContainer = styled.div`
}
`;
+const PopoverStyles = createGlobalStyle`
+ .menu-button-popover > .${Classes.POPOVER2_CONTENT} {
+ background: none;
+ }
+`;
+
export interface BaseStyleProps {
- isCompact?: boolean;
backgroundColor?: string;
+ borderRadius?: ButtonBorderRadius;
+ boxShadow?: ButtonBoxShadow;
+ boxShadowColor?: string;
+ buttonColor?: string;
+ buttonStyle?: ButtonStyle;
+ buttonVariant?: ButtonVariant;
+ isCompact?: boolean;
+ prevButtonStyle?: ButtonStyle;
textColor?: string;
}
-const BaseButton = styled(Button)`
+const BaseButton = styled(Button)`
height: 100%;
+ background-image: none !important;
+ font-weight: ${(props) => props.theme.fontWeights[2]};
+ outline: none;
+ padding: 0px 10px;
overflow: hidden;
border: 1.2px solid #ebebeb;
border-radius: 0;
box-shadow: none !important;
- background-image: none !important;
- ${({ textColor }) =>
- textColor &&
- `
- color: ${textColor} !important;
+
+ ${({ buttonColor, buttonStyle, buttonVariant, prevButtonStyle, theme }) => `
+ &:enabled {
+ background: ${
+ buttonStyle === ButtonStyleTypes.WARNING
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.warning.solid.bgColor
+ : "none"
+ : buttonStyle === ButtonStyleTypes.DANGER
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.danger.solid.bgColor
+ : "none"
+ : buttonStyle === ButtonStyleTypes.INFO
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.info.solid.bgColor
+ : "none"
+ : buttonStyle === ButtonStyleTypes.SECONDARY
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.secondary.solid.bgColor
+ : "none"
+ : buttonStyle === ButtonStyleTypes.CUSTOM
+ ? getCustomBackgroundColor(
+ theme,
+ prevButtonStyle,
+ buttonVariant,
+ buttonColor,
+ )
+ : buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.primary.solid.bgColor
+ : "none"
+ } !important;
+ }
+
+ &:hover:enabled, &:active:enabled {
+ background: ${
+ buttonStyle === ButtonStyleTypes.WARNING
+ ? buttonVariant === ButtonVariantTypes.OUTLINE
+ ? theme.colors.button.warning.outline.hoverColor
+ : buttonVariant === ButtonVariantTypes.GHOST
+ ? theme.colors.button.warning.ghost.hoverColor
+ : theme.colors.button.warning.solid.hoverColor
+ : buttonStyle === ButtonStyleTypes.DANGER
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.danger.solid.hoverColor
+ : theme.colors.button.danger.outline.hoverColor
+ : buttonStyle === ButtonStyleTypes.INFO
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.info.solid.hoverColor
+ : theme.colors.button.info.outline.hoverColor
+ : buttonStyle === ButtonStyleTypes.SECONDARY
+ ? buttonVariant === ButtonVariantTypes.OUTLINE
+ ? theme.colors.button.secondary.outline.hoverColor
+ : buttonVariant === ButtonVariantTypes.GHOST
+ ? theme.colors.button.secondary.ghost.hoverColor
+ : theme.colors.button.secondary.solid.hoverColor
+ : buttonStyle === ButtonStyleTypes.CUSTOM
+ ? getCustomHoverColor(
+ theme,
+ prevButtonStyle,
+ buttonVariant,
+ buttonColor,
+ )
+ : buttonVariant === ButtonVariantTypes.OUTLINE
+ ? theme.colors.button.primary.outline.hoverColor
+ : buttonVariant === ButtonVariantTypes.GHOST
+ ? theme.colors.button.primary.ghost.hoverColor
+ : theme.colors.button.primary.solid.hoverColor
+ } !important;
+ }
+
+ &:disabled {
+ background-color: ${theme.colors.button.disabled.bgColor} !important;
+ color: ${theme.colors.button.disabled.textColor} !important;
+ }
+
+ border: ${
+ buttonVariant === ButtonVariantTypes.OUTLINE
+ ? buttonStyle === ButtonStyleTypes.WARNING
+ ? `1px solid ${theme.colors.button.warning.outline.borderColor}`
+ : buttonStyle === ButtonStyleTypes.DANGER
+ ? `1px solid ${theme.colors.button.danger.outline.borderColor}`
+ : buttonStyle === ButtonStyleTypes.INFO
+ ? `1px solid ${theme.colors.button.info.outline.borderColor}`
+ : buttonStyle === ButtonStyleTypes.SECONDARY
+ ? `1px solid ${theme.colors.button.secondary.outline.borderColor}`
+ : buttonStyle === ButtonStyleTypes.CUSTOM
+ ? `1px solid ${getCustomBorderColor(
+ theme,
+ prevButtonStyle,
+ buttonVariant,
+ buttonColor,
+ )}`
+ : `1px solid ${theme.colors.button.primary.outline.borderColor}`
+ : "none"
+ } !important;
+
+ & > span {
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+
+ max-height: 100%;
+ overflow: hidden;
+ color: ${
+ buttonVariant === ButtonVariantTypes.SOLID
+ ? buttonStyle === ButtonStyleTypes.CUSTOM
+ ? getCustomTextColor(theme, buttonColor, prevButtonStyle)
+ : `${theme.colors.button.primary.solid.textColor}`
+ : buttonStyle === ButtonStyleTypes.WARNING
+ ? `${theme.colors.button.warning.outline.textColor}`
+ : buttonStyle === ButtonStyleTypes.DANGER
+ ? `${theme.colors.button.danger.outline.textColor}`
+ : buttonStyle === ButtonStyleTypes.INFO
+ ? `${theme.colors.button.info.outline.textColor}`
+ : buttonStyle === ButtonStyleTypes.SECONDARY
+ ? `${theme.colors.button.secondary.outline.textColor}`
+ : buttonStyle === ButtonStyleTypes.CUSTOM
+ ? getCustomBackgroundColor(
+ theme,
+ prevButtonStyle,
+ ButtonVariantTypes.SOLID,
+ buttonColor,
+ )
+ : `${theme.colors.button.primary.outline.textColor}`
+ } !important;
+ }
`}
- &:enabled {
- background-color: ${({ backgroundColor }) =>
- backgroundColor ? backgroundColor : "#ffffff"} !important;
- }
- &:hover:enabled,
- &:active:enabled {
- background-color: ${({ backgroundColor }) =>
- backgroundColor ? backgroundColor : "#ffffff"} !important;
- }
+ border-radius: ${({ borderRadius }) =>
+ borderRadius === ButtonBorderRadiusTypes.ROUNDED ? "5px" : 0};
- &:disabled {
- background-color: #a9a7a7 !important;
- color: #ffffff !important;
- }
+ box-shadow: ${({ boxShadow, boxShadowColor, theme }) =>
+ boxShadow === ButtonBoxShadowTypes.VARIANT1
+ ? `0px 0px 4px 3px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant1}`
+ : boxShadow === ButtonBoxShadowTypes.VARIANT2
+ ? `3px 3px 4px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant2}`
+ : boxShadow === ButtonBoxShadowTypes.VARIANT3
+ ? `0px 1px 3px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant3}`
+ : boxShadow === ButtonBoxShadowTypes.VARIANT4
+ ? `2px 2px 0px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant4}`
+ : boxShadow === ButtonBoxShadowTypes.VARIANT5
+ ? `-2px -2px 0px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant5}`
+ : "none"} !important;
`;
-const BaseMenuItem = styled(MenuItem)`
- ${({ backgroundColor }) =>
- backgroundColor &&
- `
+const BaseMenuItem = styled(MenuItem)`
+ ${({ backgroundColor, theme }) =>
+ backgroundColor
+ ? `
background-color: ${backgroundColor} !important;
&:hover {
background-color: ${darkenHover(backgroundColor)} !important;
@@ -64,7 +313,24 @@ const BaseMenuItem = styled(MenuItem)`
&:active {
background-color: ${darkenActive(backgroundColor)} !important;
}
- `}
+ `
+ : `
+ background: none !important
+ &:hover {
+ background-color: ${tinycolor(
+ theme.colors.button.primary.solid.textColor,
+ )
+ .darken()
+ .toString()} !important;
+ }
+ &:active {
+ background-color: ${tinycolor(
+ theme.colors.button.primary.solid.textColor,
+ )
+ .darken()
+ .toString()} !important;
+ }
+ `}
${({ textColor }) =>
textColor &&
`
@@ -81,6 +347,7 @@ const BaseMenuItem = styled(MenuItem)`
const StyledMenu = styled(Menu)`
padding: 0;
+ background: none;
`;
export interface PopoverContentProps {
@@ -155,27 +422,50 @@ function PopoverContent(props: PopoverContentProps) {
return {listItems};
}
-function ButtonNormal(props: PopoverTargetButtonProps) {
+export interface PopoverTargetButtonProps {
+ borderRadius?: ButtonBorderRadius;
+ boxShadow?: ButtonBoxShadow;
+ boxShadowColor?: string;
+ buttonStyle?: ButtonStyle;
+ buttonColor?: string;
+ buttonVariant?: ButtonVariant;
+ iconName?: IconName;
+ iconAlign?: Alignment;
+ isDisabled?: boolean;
+ label?: string;
+ prevButtonStyle?: ButtonStyle;
+}
+
+function PopoverTargetButton(props: PopoverTargetButtonProps) {
const {
- backgroundColor,
+ borderRadius,
+ boxShadow,
+ boxShadowColor,
+ buttonColor,
+ buttonStyle,
+ buttonVariant,
iconAlign,
- iconColor,
iconName,
isDisabled,
label,
- textColor,
+ prevButtonStyle,
} = props;
if (iconAlign === Alignment.RIGHT) {
return (
}
+ prevButtonStyle={prevButtonStyle}
+ rightIcon={iconName}
text={label}
- textColor={textColor}
/>
);
}
@@ -183,53 +473,23 @@ function ButtonNormal(props: PopoverTargetButtonProps) {
return (
}
+ icon={iconName}
+ prevButtonStyle={prevButtonStyle}
text={label}
- textColor={textColor}
- />
- );
-}
-
-export interface PopoverTargetButtonProps {
- label?: string;
- backgroundColor?: string;
- textColor?: string;
- iconName?: IconName;
- iconColor?: string;
- iconAlign?: Alignment;
- isDisabled?: boolean;
-}
-
-function PopoverTargetButton(props: PopoverTargetButtonProps) {
- const {
- backgroundColor,
- iconAlign,
- iconColor,
- iconName,
- isDisabled,
- label,
- textColor,
- } = props;
-
- return (
-
);
}
export interface MenuButtonComponentProps extends ComponentProps {
label?: string;
- textColor?: string;
isDisabled?: boolean;
isVisible?: boolean;
isCompact?: boolean;
@@ -250,28 +510,39 @@ export interface MenuButtonComponentProps extends ComponentProps {
onClick?: string;
}
>;
+ menuStyle?: ButtonStyle;
+ prevMenuStyle?: ButtonStyle;
+ menuVariant?: ButtonVariant;
+ menuColor?: string;
+ borderRadius?: ButtonBorderRadius;
+ boxShadow?: ButtonBoxShadow;
+ boxShadowColor?: string;
iconName?: IconName;
- iconColor?: string;
iconAlign?: Alignment;
onItemClicked: (onClick: string | undefined) => void;
}
function MenuButtonComponent(props: MenuButtonComponentProps) {
const {
- backgroundColor,
+ borderRadius,
+ boxShadow,
+ boxShadowColor,
iconAlign,
- iconColor,
iconName,
isCompact,
isDisabled,
label,
+ menuColor,
menuItems,
+ menuStyle,
+ menuVariant,
onItemClicked,
- textColor,
+ prevMenuStyle,
} = props;
return (
+
diff --git a/app/client/src/components/designSystems/appsmith/MultiSelectComponent/index.styled.tsx b/app/client/src/components/designSystems/appsmith/MultiSelectComponent/index.styled.tsx
index d50e828c0ac9..de288b419fe7 100644
--- a/app/client/src/components/designSystems/appsmith/MultiSelectComponent/index.styled.tsx
+++ b/app/client/src/components/designSystems/appsmith/MultiSelectComponent/index.styled.tsx
@@ -379,6 +379,7 @@ export const MultiSelectContainer = styled.div`
height: 100%;
display: flex;
align-items: center;
+ fill: ${Colors.SLATE_GRAY};
}
.rc-select-arrow-icon {
&::after {
diff --git a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx
index b36d27fa8a9e..be5a0db222a4 100644
--- a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx
+++ b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx
@@ -3,7 +3,7 @@ import { BaseStyle } from "widgets/BaseWidget";
import { WidgetType, WIDGET_PADDING } from "constants/WidgetConstants";
import { generateClassName } from "utils/generators";
import styled from "styled-components";
-import { useClickOpenPropPane } from "utils/hooks/useClickOpenPropPane";
+import { useClickToSelectWidget } from "utils/hooks/useClickToSelectWidget";
import { usePositionedContainerZIndex } from "utils/hooks/usePositionedContainerZIndex";
import { useSelector } from "react-redux";
import { snipingModeSelector } from "../../../selectors/editorSelectors";
@@ -27,7 +27,7 @@ export function PositionedContainer(props: PositionedContainerProps) {
const x = props.style.xPosition + (props.style.xPositionUnit || "px");
const y = props.style.yPosition + (props.style.yPositionUnit || "px");
const padding = WIDGET_PADDING;
- const openPropertyPane = useClickOpenPropPane();
+ const clickToSelectWidget = useClickToSelectWidget();
const isSnipingMode = useSelector(snipingModeSelector);
// memoized classname
const containerClassName = useMemo(() => {
@@ -55,11 +55,11 @@ export function PositionedContainer(props: PositionedContainerProps) {
};
}, [props.style, onHoverZIndex, zIndex]);
- const openPropPane = useCallback(
+ const onClickFn = useCallback(
(e) => {
- openPropertyPane(e, props.widgetId);
+ clickToSelectWidget(e, props.widgetId);
},
- [props.widgetId, openPropertyPane],
+ [props.widgetId, clickToSelectWidget],
);
// TODO: Experimental fix for sniping mode. This should be handled with a single event
@@ -75,7 +75,7 @@ export function PositionedContainer(props: PositionedContainerProps) {
key={`positioned-container-${props.widgetId}`}
// Positioned Widget is the top enclosure for all widgets and clicks on/inside the widget should not be propogated/bubbled out of this Container.
onClick={stopEventPropagation}
- onClickCapture={openPropPane}
+ onClickCapture={onClickFn}
//Before you remove: This is used by property pane to reference the element
style={containerStyle}
zIndexOnHover={onHoverZIndex}
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts
index 2cd81dc3367e..b06516868905 100644
--- a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts
@@ -62,6 +62,11 @@ export enum OperatorTypes {
AND = "AND",
}
+export enum SortOrderTypes {
+ asc = "asc",
+ desc = "desc",
+}
+
export interface TableStyles {
cellBackground?: string;
textColor?: string;
diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableHeader.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableHeader.tsx
index 9999d26689ce..12dd0220d517 100644
--- a/app/client/src/components/designSystems/appsmith/TableComponent/TableHeader.tsx
+++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableHeader.tsx
@@ -1,6 +1,6 @@
-import React, { useEffect } from "react";
+import React, { useEffect, useCallback } from "react";
import styled from "styled-components";
-import { Icon, NumericInput } from "@blueprintjs/core";
+import { Icon, NumericInput, Keys } from "@blueprintjs/core";
import {
RowWrapper,
PaginationWrapper,
@@ -52,6 +52,28 @@ function PageNumberInput(props: {
useEffect(() => {
setPageNumber(props.pageNo || 0);
}, [props.pageNo]);
+ const handleUpdatePageNo = useCallback(
+ (e) => {
+ const oldPageNo = Number(props.pageNo || 0);
+ let page = Number(e.target.value);
+ // check page is less then min page count
+ if (isNaN(page) || page < 1) {
+ page = 1;
+ }
+ // check page is greater then max page count
+ if (page > props.pageCount) {
+ page = props.pageCount;
+ }
+ // fire Event based on new page number
+ if (oldPageNo < page) {
+ props.updatePageNo(page, EventType.ON_NEXT_PAGE);
+ } else if (oldPageNo > page) {
+ props.updatePageNo(page, EventType.ON_PREV_PAGE);
+ }
+ setPageNumber(page);
+ },
+ [props.pageNo, props.pageCount],
+ );
return (
{
- const oldPageNo = Number(props.pageNo || 0);
- const value = e.target.value;
- let page = Number(value);
- if (isNaN(value) || Number(value) < 1) {
- page = 1;
- }
- if (oldPageNo < page) {
- props.updatePageNo(page, EventType.ON_NEXT_PAGE);
- } else if (oldPageNo > page) {
- props.updatePageNo(page, EventType.ON_PREV_PAGE);
+ onBlur={handleUpdatePageNo}
+ onKeyDown={(e: any) => {
+ if (e.keyCode === Keys.ENTER) {
+ handleUpdatePageNo(e);
}
}}
onValueChange={(value: number) => {
- if (isNaN(value) || value < 1) {
- setPageNumber(1);
- } else if (value > props.pageCount) {
- setPageNumber(props.pageCount);
- } else {
- setPageNumber(value);
- }
+ setPageNumber(value);
}}
value={pageNumber}
/>
diff --git a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx
index c4adfb05317b..7fc4f995b8f0 100644
--- a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx
+++ b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx
@@ -1,16 +1,18 @@
import React, { useRef, useState } from "react";
+import styled from "styled-components";
+import tinycolor from "tinycolor2";
+
import {
IButtonProps,
MaybeElement,
Button,
- IconName,
+ Alignment,
Position,
} from "@blueprintjs/core";
+import { IconName } from "@blueprintjs/icons";
+
import Tooltip from "components/ads/Tooltip";
-import styled, { css } from "styled-components";
-import { ButtonStyle } from "widgets/ButtonWidget";
-import { Theme, darkenHover, darkenActive } from "constants/DefaultTheme";
-import _ from "lodash";
+import { Theme } from "constants/DefaultTheme";
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
import { useScript, ScriptStatus } from "utils/hooks/useScript";
import {
@@ -18,38 +20,126 @@ import {
GOOGLE_RECAPTCHA_DOMAIN_ERROR,
createMessage,
} from "constants/messages";
-import { Variant } from "components/ads/common";
+import { ThemeProp, Variant } from "components/ads/common";
import { Toaster } from "components/ads/Toast";
import ReCAPTCHA from "react-google-recaptcha";
+import {
+ ButtonBoxShadow,
+ ButtonBoxShadowTypes,
+} from "components/propertyControls/BoxShadowOptionsControl";
+import {
+ ButtonBorderRadius,
+ ButtonBorderRadiusTypes,
+} from "components/propertyControls/BorderRadiusOptionsControl";
-const getButtonColorStyles = (props: { theme: Theme } & ButtonStyleProps) => {
- if (props.filled) {
- return props.accent === "grey"
- ? props.theme.colors.textOnGreyBG
- : props.theme.colors.textOnDarkBG;
- }
- if (props.accent) {
- if (props.accent === "secondary") {
- return props.theme.colors[AccentColorMap["primary"]];
- }
- return props.theme.colors[AccentColorMap[props.accent]];
+export enum ButtonStyleTypes {
+ PRIMARY = "PRIMARY",
+ WARNING = "WARNING",
+ DANGER = "DANGER",
+ INFO = "INFO",
+ SECONDARY = "SECONDARY",
+ CUSTOM = "CUSTOM",
+}
+export type ButtonStyle = keyof typeof ButtonStyleTypes;
+
+export enum ButtonVariantTypes {
+ SOLID = "SOLID",
+ OUTLINE = "OUTLINE",
+ GHOST = "GHOST",
+}
+export type ButtonVariant = keyof typeof ButtonVariantTypes;
+
+const getCustomTextColor = (
+ theme: Theme,
+ backgroundColor?: string,
+ prevButtonStyle?: ButtonStyle,
+) => {
+ if (!backgroundColor)
+ return theme.colors.button[
+ (prevButtonStyle || ButtonStyleTypes.PRIMARY).toLowerCase()
+ ].solid.textColor;
+ const isDark = tinycolor(backgroundColor).isDark();
+ if (isDark) {
+ return theme.colors.button.custom.solid.light.textColor;
}
+ return theme.colors.button.custom.solid.dark.textColor;
};
-const getButtonFillStyles = (props: { theme: Theme } & ButtonStyleProps) => {
- if (props.filled) {
- return props.accent === "grey"
- ? props.theme.colors.dropdownIconDarkBg
- : props.theme.colors.textOnDarkBG;
+const getCustomHoverColor = (
+ theme: Theme,
+ prevButtonStyle?: ButtonStyle,
+ buttonVariant?: ButtonVariant,
+ backgroundColor?: string,
+) => {
+ if (!backgroundColor) {
+ return theme.colors.button[
+ (prevButtonStyle || ButtonStyleTypes.PRIMARY).toLowerCase()
+ ][(buttonVariant || ButtonVariantTypes.SOLID).toLowerCase()].hoverColor;
}
- if (props.accent) {
- if (props.accent === "secondary") {
- return props.theme.colors[AccentColorMap["primary"]];
- }
- return props.theme.colors[AccentColorMap[props.accent]];
+
+ switch (buttonVariant) {
+ case ButtonVariantTypes.OUTLINE:
+ return backgroundColor
+ ? tinycolor(backgroundColor)
+ .lighten(40)
+ .toString()
+ : theme.colors.button.primary.outline.hoverColor;
+ break;
+ case ButtonVariantTypes.GHOST:
+ return backgroundColor
+ ? tinycolor(backgroundColor)
+ .lighten(40)
+ .toString()
+ : theme.colors.button.primary.ghost.hoverColor;
+ break;
+
+ default:
+ return backgroundColor
+ ? tinycolor(backgroundColor)
+ .darken(10)
+ .toString()
+ : theme.colors.button.primary.solid.hoverColor;
+ break;
}
};
+const getCustomBackgroundColor = (
+ theme: Theme,
+ prevButtonStyle?: ButtonStyle,
+ buttonVariant?: ButtonVariant,
+ backgroundColor?: string,
+) => {
+ return buttonVariant === ButtonVariantTypes.SOLID
+ ? backgroundColor
+ ? backgroundColor
+ : theme.colors.button[
+ (prevButtonStyle || ButtonStyleTypes.PRIMARY).toLowerCase()
+ ].solid.bgColor
+ : "none";
+};
+
+const getCustomBorderColor = (
+ theme: Theme,
+ prevButtonStyle?: ButtonStyle,
+ buttonVariant?: ButtonVariant,
+ backgroundColor?: string,
+) => {
+ return buttonVariant === ButtonVariantTypes.OUTLINE
+ ? backgroundColor
+ ? backgroundColor
+ : theme.colors.button[
+ (prevButtonStyle || ButtonStyleTypes.PRIMARY).toLowerCase()
+ ].outline.borderColor
+ : "none";
+};
+
+const RecaptchaWrapper = styled.div`
+ position: relative;
+ .grecaptcha-badge {
+ visibility: hidden;
+ }
+`;
+
const ToolTipContent = styled.div`
max-width: 350px;
`;
@@ -64,118 +154,257 @@ const ToolTipWrapper = styled.div`
}
`;
-const ButtonColorStyles = css`
- color: ${getButtonColorStyles};
- svg {
- fill: ${getButtonFillStyles};
- }
-`;
+const ButtonContainer = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
-const RecaptchaWrapper = styled.div`
- position: relative;
- .grecaptcha-badge {
- visibility: hidden;
+ & > button {
+ height: 100%;
}
`;
-const AccentColorMap: Record = {
- primary: "primaryOld",
- secondary: "secondaryOld",
- error: "error",
- grey: "dropdownGreyBg",
-};
+const StyledButton = styled(Button)`
+ height: 100%;
+ background-image: none !important;
+ font-weight: ${(props) => props.theme.fontWeights[2]};
+ outline: none;
+ padding: 0px 10px;
-const ButtonWrapper = styled((props: ButtonStyleProps & IButtonProps) => (
-
-))`
- &&&& {
- ${ButtonColorStyles};
- width: 100%;
- height: 100%;
- transition: background-color 0.2s;
- background-color: ${(props) =>
- props.filled &&
- props.accent &&
- props.theme.colors[AccentColorMap[props.accent]]};
- border: 1px solid
- ${(props) =>
- props.accent
- ? props.theme.colors[AccentColorMap[props.accent]]
- : props.theme.colors.primary};
-
- border-radius: 0;
- font-weight: ${(props) => props.theme.fontWeights[2]};
- outline: none;
-
- &.bp3-button {
- padding: 0px 10px;
+ ${({ buttonColor, buttonStyle, buttonVariant, prevButtonStyle, theme }) => `
+ &:enabled {
+ background: ${
+ buttonStyle === ButtonStyleTypes.WARNING
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.warning.solid.bgColor
+ : "none"
+ : buttonStyle === ButtonStyleTypes.DANGER
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.danger.solid.bgColor
+ : "none"
+ : buttonStyle === ButtonStyleTypes.INFO
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.info.solid.bgColor
+ : "none"
+ : buttonStyle === ButtonStyleTypes.SECONDARY
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.secondary.solid.bgColor
+ : "none"
+ : buttonStyle === ButtonStyleTypes.CUSTOM
+ ? getCustomBackgroundColor(
+ theme,
+ prevButtonStyle,
+ buttonVariant,
+ buttonColor,
+ )
+ : buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.primary.solid.bgColor
+ : "none"
+ } !important;
}
- && .bp3-button-text {
+
+ &:hover:enabled, &:active:enabled {
+ background: ${
+ buttonStyle === ButtonStyleTypes.WARNING
+ ? buttonVariant === ButtonVariantTypes.OUTLINE
+ ? theme.colors.button.warning.outline.hoverColor
+ : buttonVariant === ButtonVariantTypes.GHOST
+ ? theme.colors.button.warning.ghost.hoverColor
+ : theme.colors.button.warning.solid.hoverColor
+ : buttonStyle === ButtonStyleTypes.DANGER
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.danger.solid.hoverColor
+ : theme.colors.button.danger.outline.hoverColor
+ : buttonStyle === ButtonStyleTypes.INFO
+ ? buttonVariant === ButtonVariantTypes.SOLID
+ ? theme.colors.button.info.solid.hoverColor
+ : theme.colors.button.info.outline.hoverColor
+ : buttonStyle === ButtonStyleTypes.SECONDARY
+ ? buttonVariant === ButtonVariantTypes.OUTLINE
+ ? theme.colors.button.secondary.outline.hoverColor
+ : buttonVariant === ButtonVariantTypes.GHOST
+ ? theme.colors.button.secondary.ghost.hoverColor
+ : theme.colors.button.secondary.solid.hoverColor
+ : buttonStyle === ButtonStyleTypes.CUSTOM
+ ? getCustomHoverColor(
+ theme,
+ prevButtonStyle,
+ buttonVariant,
+ buttonColor,
+ )
+ : buttonVariant === ButtonVariantTypes.OUTLINE
+ ? theme.colors.button.primary.outline.hoverColor
+ : buttonVariant === ButtonVariantTypes.GHOST
+ ? theme.colors.button.primary.ghost.hoverColor
+ : theme.colors.button.primary.solid.hoverColor
+ } !important;
+ }
+
+ &:disabled {
+ background-color: ${theme.colors.button.disabled.bgColor} !important;
+ color: ${theme.colors.button.disabled.textColor} !important;
+ }
+
+ border: ${
+ buttonVariant === ButtonVariantTypes.OUTLINE
+ ? buttonStyle === ButtonStyleTypes.WARNING
+ ? `1px solid ${theme.colors.button.warning.outline.borderColor}`
+ : buttonStyle === ButtonStyleTypes.DANGER
+ ? `1px solid ${theme.colors.button.danger.outline.borderColor}`
+ : buttonStyle === ButtonStyleTypes.INFO
+ ? `1px solid ${theme.colors.button.info.outline.borderColor}`
+ : buttonStyle === ButtonStyleTypes.SECONDARY
+ ? `1px solid ${theme.colors.button.secondary.outline.borderColor}`
+ : buttonStyle === ButtonStyleTypes.CUSTOM
+ ? `1px solid ${getCustomBorderColor(
+ theme,
+ prevButtonStyle,
+ buttonVariant,
+ buttonColor,
+ )}`
+ : `1px solid ${theme.colors.button.primary.outline.borderColor}`
+ : "none"
+ } !important;
+
+ & > span {
+ max-height: 100%;
max-width: 99%;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
- max-height: 100%;
- overflow: hidden;
- }
- &&:hover,
- &&:focus {
- ${ButtonColorStyles};
- background-color: ${(props) => {
- if (!props.filled) return props.theme.colors.secondaryDarker;
- if (props.accent !== "secondary" && props.accent) {
- return darkenHover(props.theme.colors[AccentColorMap[props.accent]]);
- }
- }};
- border-color: ${(props) => {
- if (!props.filled) return;
- if (props.accent !== "secondary" && props.accent) {
- return darkenHover(props.theme.colors[AccentColorMap[props.accent]]);
- }
- }};
+ color: ${
+ buttonVariant === ButtonVariantTypes.SOLID
+ ? buttonStyle === ButtonStyleTypes.CUSTOM
+ ? getCustomTextColor(theme, buttonColor, prevButtonStyle)
+ : `${theme.colors.button.primary.solid.textColor}`
+ : buttonStyle === ButtonStyleTypes.WARNING
+ ? `${theme.colors.button.warning.outline.textColor}`
+ : buttonStyle === ButtonStyleTypes.DANGER
+ ? `${theme.colors.button.danger.outline.textColor}`
+ : buttonStyle === ButtonStyleTypes.INFO
+ ? `${theme.colors.button.info.outline.textColor}`
+ : buttonStyle === ButtonStyleTypes.SECONDARY
+ ? `${theme.colors.button.secondary.outline.textColor}`
+ : buttonStyle === ButtonStyleTypes.CUSTOM
+ ? getCustomBackgroundColor(
+ theme,
+ prevButtonStyle,
+ ButtonVariantTypes.SOLID,
+ buttonColor,
+ )
+ : `${theme.colors.button.primary.outline.textColor}`
+ } !important;
}
- &&:active {
- ${ButtonColorStyles};
- background-color: ${(props) => {
- if (!props.filled) return props.theme.colors.secondaryDarkest;
- if (props.accent !== "secondary" && props.accent) {
- return darkenActive(props.theme.colors[AccentColorMap[props.accent]]);
- }
- }};
- border-color: ${(props) => {
- if (!props.filled) return;
- if (props.accent !== "secondary" && props.accent) {
- return darkenActive(props.theme.colors[AccentColorMap[props.accent]]);
- }
- }};
- }
- &&.bp3-disabled {
- background-color: #d0d7dd;
- border: none;
- }
- }
-`;
+ `}
-export type ButtonStyleName = "primary" | "secondary" | "error" | "grey";
+
+ border-radius: ${({ borderRadius }) =>
+ borderRadius === ButtonBorderRadiusTypes.ROUNDED ? "5px" : 0};
+
+ box-shadow: ${({ boxShadow, boxShadowColor, theme }) =>
+ boxShadow === ButtonBoxShadowTypes.VARIANT1
+ ? `0px 0px 4px 3px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant1}`
+ : boxShadow === ButtonBoxShadowTypes.VARIANT2
+ ? `3px 3px 4px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant2}`
+ : boxShadow === ButtonBoxShadowTypes.VARIANT3
+ ? `0px 1px 3px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant3}`
+ : boxShadow === ButtonBoxShadowTypes.VARIANT4
+ ? `2px 2px 0px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant4}`
+ : boxShadow === ButtonBoxShadowTypes.VARIANT5
+ ? `-2px -2px 0px ${boxShadowColor ||
+ theme.colors.button.boxShadow.default.variant5}`
+ : "none"} !important;
+`;
type ButtonStyleProps = {
- accent?: ButtonStyleName;
- filled?: boolean;
+ buttonColor?: string;
+ buttonStyle?: ButtonStyle;
+ prevButtonStyle?: ButtonStyle;
+ buttonVariant?: ButtonVariant;
+ boxShadow?: ButtonBoxShadow;
+ boxShadowColor?: string;
+ borderRadius?: ButtonBorderRadius;
+ iconName?: IconName;
+ iconAlign?: Alignment;
};
// To be used in any other part of the app
export function BaseButton(props: IButtonProps & ButtonStyleProps) {
- const className = props.disabled
- ? `${props.className} bp3-disabled`
- : props.className;
- return ;
+ const {
+ borderRadius,
+ boxShadow,
+ boxShadowColor,
+ buttonColor,
+ buttonStyle,
+ buttonVariant,
+ className,
+ disabled,
+ icon,
+ iconAlign,
+ iconName,
+ loading,
+ onClick,
+ prevButtonStyle,
+ rightIcon,
+ text,
+ } = props;
+
+ if (iconAlign === Alignment.RIGHT) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
}
BaseButton.defaultProps = {
- accent: "secondary",
+ buttonStyle: "SECONDARY",
+ buttonVariant: "SOLID",
disabled: false,
text: "Button Text",
minimal: true,
@@ -194,31 +423,26 @@ interface RecaptchaProps {
recaptchaV2?: boolean;
}
-interface ButtonContainerProps extends ComponentProps {
+interface ButtonComponentProps extends ComponentProps {
text?: string;
- icon?: MaybeElement;
+ icon?: IconName | MaybeElement;
tooltip?: string;
onClick?: (event: React.MouseEvent) => void;
- disabled?: boolean;
+ isDisabled?: boolean;
buttonStyle?: ButtonStyle;
+ prevButtonStyle?: ButtonStyle;
isLoading: boolean;
rightIcon?: IconName | MaybeElement;
type: ButtonType;
+ buttonColor?: string;
+ buttonVariant?: ButtonVariant;
+ borderRadius?: ButtonBorderRadius;
+ boxShadow?: ButtonBoxShadow;
+ boxShadowColor?: string;
+ iconName?: IconName;
+ iconAlign?: Alignment;
}
-const mapButtonStyleToStyleName = (buttonStyle?: ButtonStyle) => {
- switch (buttonStyle) {
- case "PRIMARY_BUTTON":
- return "primary";
- case "SECONDARY_BUTTON":
- return "secondary";
- case "DANGER_BUTTON":
- return "error";
- default:
- return undefined;
- }
-};
-
function RecaptchaV2Component(
props: {
children: any;
@@ -348,9 +572,7 @@ function BtnWrapper(
}
// To be used with the canvas
-function ButtonContainer(
- props: ButtonContainerProps & ButtonStyleProps & RecaptchaProps,
-) {
+function ButtonComponent(props: ButtonComponentProps & RecaptchaProps) {
const btnWrapper = (
-
+
+
+
);
if (props.tooltip) {
@@ -388,4 +619,4 @@ function ButtonContainer(
}
}
-export default ButtonContainer;
+export default ButtonComponent;
diff --git a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx
index 5b7085811692..daa2d5d6faa2 100644
--- a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx
+++ b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx
@@ -32,7 +32,7 @@ const FUSE_OPTIONS = {
};
const SingleDropDown = Select.ofType();
-const StyledSingleDropDown = styled(SingleDropDown)`
+const StyledSingleDropDown = styled(SingleDropDown)<{ isSelected: boolean }>`
div {
flex: 1 1 auto;
}
@@ -60,6 +60,8 @@ const StyledSingleDropDown = styled(SingleDropDown)`
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
+ color: ${(props) =>
+ props.isSelected ? Colors.SELECT_COLOR : Colors.SELECT_PLACEHOLDER};
}
&& {
.${Classes.ICON} {
@@ -171,6 +173,11 @@ class DropDownComponent extends React.Component {
className={this.props.isLoading ? Classes.SKELETON : ""}
disabled={this.props.disabled}
filterable={this.props.isFilterable}
+ isSelected={
+ !_.isEmpty(this.props.options) &&
+ this.props.selectedIndex !== undefined &&
+ this.props.selectedIndex > -1
+ }
itemListPredicate={
!this.props.serverSideFiltering
? this.itemListPredicate
@@ -202,7 +209,7 @@ class DropDownComponent extends React.Component {
this.props.selectedIndex !== undefined &&
this.props.selectedIndex > -1
? this.props.options[this.props.selectedIndex].label
- : "-- Select --"
+ : this.props.placeholder || "-- Select --"
}
/>
diff --git a/app/client/src/components/designSystems/blueprint/InputComponent.tsx b/app/client/src/components/designSystems/blueprint/InputComponent/index.tsx
similarity index 76%
rename from app/client/src/components/designSystems/blueprint/InputComponent.tsx
rename to app/client/src/components/designSystems/blueprint/InputComponent/index.tsx
index c4a846937bb0..875a4678fca1 100644
--- a/app/client/src/components/designSystems/blueprint/InputComponent.tsx
+++ b/app/client/src/components/designSystems/blueprint/InputComponent/index.tsx
@@ -36,9 +36,14 @@ import {
createMessage,
INPUT_WIDGET_DEFAULT_VALIDATION_ERROR,
} from "constants/messages";
-import Dropdown, { DropdownOption } from "components/ads/Dropdown";
-import { CurrencyTypeOptions, CurrencyOptionProps } from "constants/Currency";
-import Icon, { IconSize } from "components/ads/Icon";
+import CurrencyTypeDropdown, {
+ CurrencyDropdownOptions,
+ getSelectedCurrency,
+} from "components/ads/CurrencyCodeDropdown";
+import ISDCodeDropdown, {
+ ISDCodeDropdownOptions,
+ getSelectedISDCode,
+} from "components/ads/ISDCodeDropdown";
/**
* All design system component specific logic goes here.
* Ex. Blueprint has a separate numeric input and text input so switching between them goes here
@@ -53,11 +58,13 @@ const InputComponentWrapper = styled((props) => (
multiline: string;
hasError: boolean;
allowCurrencyChange?: boolean;
+ disabled?: boolean;
inputType: InputType;
}>`
flex-direction: ${(props) => (props.compactMode ? "row" : "column")};
&&&& {
- .currency-type-filter {
+ .currency-type-filter,
+ .country-type-filter {
width: 40px;
height: 32px;
position: absolute;
@@ -81,6 +88,8 @@ const InputComponentWrapper = styled((props) => (
!props.allowCurrencyChange &&
`
padding-left: 35px;`};
+ ${(props) =>
+ props.inputType === InputTypes.PHONE_NUMBER && `padding-left: 85px;`};
box-shadow: none;
border: 1px solid;
border-color: ${({ hasError }) =>
@@ -171,116 +180,14 @@ const TextInputWrapper = styled.div`
flex: 1;
`;
-const DropdownTriggerIconWrapper = styled.div`
- height: 19px;
- padding: 9px 5px 9px 12px;
- width: 40px;
- height: 19px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: 14px;
- line-height: 19px;
- letter-spacing: -0.24px;
- color: #090707;
-`;
-
-const CurrencyIconWrapper = styled.span`
- height: 100%;
- padding: 6px 4px 6px 12px;
- width: 28px;
- position: absolute;
- left: 0;
- z-index: 16;
- font-size: 14px;
- line-height: 19px;
- letter-spacing: -0.24px;
- color: #090707;
-`;
-
-interface CurrencyDropdownProps {
- onCurrencyTypeChange: (code?: string) => void;
- options: Array;
- selected: DropdownOption;
- allowCurrencyChange?: boolean;
-}
-
-function CurrencyTypeDropdown(props: CurrencyDropdownProps) {
- if (!props.allowCurrencyChange) {
- return (
-
- {getSelectedItem(props.selected.value).id}
-
- );
- }
- const dropdownTriggerIcon = (
-
- {getSelectedItem(props.selected.value).id}
-
-
- );
+export const isNumberInputType = (inputType: InputType) => {
return (
-
+ inputType === "INTEGER" ||
+ inputType === "NUMBER" ||
+ inputType === "CURRENCY" ||
+ inputType === "PHONE_NUMBER"
);
-}
-
-const getSelectedItem = (currencyCountryCode?: string): DropdownOption => {
- let selectedCurrency: CurrencyOptionProps | undefined = currencyCountryCode
- ? CurrencyTypeOptions.find((item: CurrencyOptionProps) => {
- return item.code === currencyCountryCode;
- })
- : undefined;
- if (!selectedCurrency) {
- selectedCurrency = {
- code: "US",
- currency: "USD",
- currency_name: "US Dollar",
- label: "United States",
- phone: "1",
- symbol_native: "$",
- };
- }
- return {
- label: `${selectedCurrency.currency} - ${selectedCurrency.currency_name}`,
- searchText: selectedCurrency.label,
- value: selectedCurrency.code,
- id: selectedCurrency.symbol_native,
- };
};
-
-const countryToFlag = (isoCode: string) => {
- return typeof String.fromCodePoint !== "undefined"
- ? isoCode
- .toUpperCase()
- .replace(/./g, (char) =>
- String.fromCodePoint(char.charCodeAt(0) + 127397),
- )
- : isoCode;
-};
-
-export const getCurrencyOptions = (): Array => {
- return CurrencyTypeOptions.map((item: CurrencyOptionProps) => {
- return {
- leftElement: countryToFlag(item.code),
- searchText: item.label,
- label: `${item.currency} - ${item.currency_name}`,
- value: item.code,
- id: item.symbol_native,
- };
- });
-};
-
class InputComponent extends React.Component<
InputComponentProps,
InputComponentState
@@ -334,18 +241,39 @@ class InputComponent extends React.Component<
}
};
- isNumberInputType(inputType: InputType) {
- return (
- inputType === "INTEGER" ||
- inputType === "NUMBER" ||
- inputType === "CURRENCY"
- );
- }
+ getLeftIcon = (inputType: InputType, disabled: boolean) => {
+ if (inputType === InputTypes.PHONE_NUMBER) {
+ const selectedISDCode = getSelectedISDCode(
+ this.props.phoneNumberCountryCode,
+ );
+ return (
+
+ );
+ } else if (inputType === InputTypes.CURRENCY) {
+ const selectedCurrencyCountryCode = getSelectedCurrency(
+ this.props.currencyCountryCode,
+ );
+ return (
+
+ );
+ } else if (this.props.iconName && this.props.iconAlign === "left") {
+ return this.props.iconName;
+ }
+ return this.props.leftIcon;
+ };
getIcon(inputType: InputType) {
switch (inputType) {
- case "PHONE_NUMBER":
- return "phone";
case "SEARCH":
return "search";
case "EMAIL":
@@ -384,6 +312,10 @@ class InputComponent extends React.Component<
};
private numericInputComponent = () => {
+ const leftIcon = this.getLeftIcon(
+ this.props.inputType,
+ !!this.props.disabled,
+ );
const minorStepSize =
this.props.inputType === InputTypes.CURRENCY
? this.props.decimalsInCurrency || 0
@@ -394,25 +326,14 @@ class InputComponent extends React.Component<
className={this.props.isLoading ? "bp3-skeleton" : Classes.FILL}
disabled={this.props.disabled}
intent={this.props.intent}
- leftIcon={
- this.props.inputType === "PHONE_NUMBER" ? (
- "phone"
- ) : this.props.inputType === InputTypes.CURRENCY ? (
-
- ) : this.props.iconName && this.props.iconAlign === "left" ? (
- this.props.iconName
- ) : (
- undefined
- )
- }
+ leftIcon={leftIcon}
max={this.props.maxNum}
maxLength={this.props.maxChars}
- min={this.props.minNum}
+ min={
+ this.props.inputType === InputTypes.PHONE_NUMBER
+ ? 0
+ : this.props.minNum
+ }
minorStepSize={
minorStepSize === 0 ? undefined : Math.pow(10, -1 * minorStepSize)
}
@@ -422,7 +343,6 @@ class InputComponent extends React.Component<
onValueChange={this.onNumberChange}
placeholder={this.props.placeholder}
stepSize={minorStepSize === 0 ? this.props.stepSize : undefined}
- type={this.props.inputType === "PHONE_NUMBER" ? "tel" : undefined}
value={this.props.value}
/>
);
@@ -484,7 +404,7 @@ class InputComponent extends React.Component<
/>
);
private renderInputComponent = (inputType: InputType, isTextArea: boolean) =>
- this.isNumberInputType(inputType)
+ isNumberInputType(inputType)
? this.numericInputComponent()
: this.textInputComponent(isTextArea);
@@ -502,6 +422,7 @@ class InputComponent extends React.Component<
{showLabelHeader && (
void;
onCurrencyTypeChange: (code?: string) => void;
+ onISDCodeChange: (code?: string) => void;
stepSize?: number;
placeholder?: string;
isLoading: boolean;
diff --git a/app/client/src/components/designSystems/blueprint/InputComponent/utilties.tsx b/app/client/src/components/designSystems/blueprint/InputComponent/utilties.tsx
new file mode 100644
index 000000000000..27a117e5f558
--- /dev/null
+++ b/app/client/src/components/designSystems/blueprint/InputComponent/utilties.tsx
@@ -0,0 +1,9 @@
+export const countryToFlag = (isoCode: string) => {
+ return typeof String.fromCodePoint !== "undefined"
+ ? isoCode
+ .toUpperCase()
+ .replace(/./g, (char) =>
+ String.fromCodePoint(char.charCodeAt(0) + 127397),
+ )
+ : isoCode;
+};
diff --git a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx
index eab10d531ba9..5a732af368bf 100644
--- a/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx
+++ b/app/client/src/components/editorComponents/ActionRightPane/SuggestedWidgets.tsx
@@ -18,7 +18,7 @@ import { SuggestedWidget } from "api/ActionAPI";
import { useSelector } from "store";
import { getDataTree } from "selectors/dataTreeSelectors";
import { getWidgets } from "sagas/selectors";
-import { getNextWidgetName } from "sagas/WidgetOperationSagas";
+import { getNextWidgetName } from "sagas/WidgetOperationUtils";
const WidgetList = styled.div`
${(props) => getTypographyByKey(props, "p1")}
diff --git a/app/client/src/components/editorComponents/CodeEditor/index.tsx b/app/client/src/components/editorComponents/CodeEditor/index.tsx
index 6f2a233b8b0e..d795affe4825 100644
--- a/app/client/src/components/editorComponents/CodeEditor/index.tsx
+++ b/app/client/src/components/editorComponents/CodeEditor/index.tsx
@@ -1,7 +1,11 @@
import React, { Component } from "react";
import { connect } from "react-redux";
import { AppState } from "reducers";
-import CodeMirror, { EditorConfiguration } from "codemirror";
+import CodeMirror, {
+ Annotation,
+ EditorConfiguration,
+ UpdateLintingCallback,
+} from "codemirror";
import "codemirror/lib/codemirror.css";
import "codemirror/theme/duotone-dark.css";
import "codemirror/theme/duotone-light.css";
@@ -12,6 +16,9 @@ import "codemirror/addon/edit/closebrackets";
import "codemirror/addon/display/autorefresh";
import "codemirror/addon/mode/multiplex";
import "codemirror/addon/tern/tern.css";
+import "codemirror/addon/lint/lint";
+import "codemirror/addon/lint/lint.css";
+
import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
import { WrappedFieldInputProps } from "redux-form";
@@ -70,6 +77,8 @@ import { getPluginIdToImageLocation } from "sagas/selectors";
import { ExpectedValueExample } from "utils/validation/common";
import { getRecentEntityIds } from "selectors/globalSearchSelectors";
import { AutocompleteDataType } from "utils/autocomplete/TernServer";
+import { getLintAnnotations } from "./lintHelpers";
+import getFeatureFlags from "utils/featureFlags";
const AUTOCOMPLETE_CLOSE_KEY_CODES = [
"Enter",
@@ -147,8 +156,9 @@ class CodeEditor extends Component {
codeEditorTarget = React.createRef();
editor!: CodeMirror.Editor;
hinters: Hinter[] = [];
+ annotations: Annotation[] = [];
+ updateLintingCallback: UpdateLintingCallback | undefined;
private editorWrapperRef = React.createRef();
-
constructor(props: Props) {
super(props);
this.state = {
@@ -176,6 +186,13 @@ class CodeEditor extends Component {
scrollbarStyle:
this.props.size !== EditorSize.COMPACT ? "native" : "null",
placeholder: this.props.placeholder,
+ lint: {
+ getAnnotations: (_: string, callback: UpdateLintingCallback) => {
+ this.updateLintingCallback = callback;
+ },
+ async: true,
+ lintOnChange: false,
+ },
};
if (!this.props.input.onChange || this.props.disabled) {
@@ -326,7 +343,11 @@ class CodeEditor extends Component {
handleEditorFocus = () => {
this.setState({ isFocused: true });
- if (this.editor.getValue().length === 0)
+ const entityInformation = this.getEntityInformation();
+ if (
+ entityInformation.entityType === ENTITY_TYPE.WIDGET &&
+ this.editor.getValue().length === 0
+ )
this.handleAutocompleteVisibility(this.editor);
};
@@ -372,31 +393,43 @@ class CodeEditor extends Component {
CodeEditor.updateMarkings(this.editor, this.props.marking);
};
- handleAutocompleteVisibility = (cm: CodeMirror.Editor) => {
- if (!this.state.isFocused) return;
+ getEntityInformation = (): FieldEntityInformation => {
const { dataTreePath, dynamicData, expected } = this.props;
const entityInformation: FieldEntityInformation = {
expectedType: expected?.autocompleteDataType,
};
+
if (dataTreePath) {
const { entityName, propertyPath } = getEntityNameAndPropertyPath(
dataTreePath,
);
entityInformation.entityName = entityName;
const entity = dynamicData[entityName];
- if (entity && "ENTITY_TYPE" in entity) {
- const entityType = entity.ENTITY_TYPE;
- if (
- entityType === ENTITY_TYPE.WIDGET ||
- entityType === ENTITY_TYPE.ACTION
- ) {
- entityInformation.entityType = entityType;
+
+ if (entity) {
+ if ("ENTITY_TYPE" in entity) {
+ const entityType = entity.ENTITY_TYPE;
+ if (
+ entityType === ENTITY_TYPE.WIDGET ||
+ entityType === ENTITY_TYPE.ACTION
+ ) {
+ entityInformation.entityType = entityType;
+ }
}
+
+ if (isActionEntity(entity))
+ entityInformation.entityId = entity.actionId;
+ if (isWidgetEntity(entity))
+ entityInformation.entityId = entity.widgetId;
+ entityInformation.propertyPath = propertyPath;
}
- if (isActionEntity(entity)) entityInformation.entityId = entity.actionId;
- if (isWidgetEntity(entity)) entityInformation.entityId = entity.widgetId;
- entityInformation.propertyPath = propertyPath;
}
+ return entityInformation;
+ };
+
+ handleAutocompleteVisibility = (cm: CodeMirror.Editor) => {
+ if (!this.state.isFocused) return;
+ const entityInformation: FieldEntityInformation = this.getEntityInformation();
let hinterOpen = false;
for (let i = 0; i < this.hinters.length; i++) {
hinterOpen = this.hinters[i].showHint(cm, entityInformation, {
@@ -425,6 +458,28 @@ class CodeEditor extends Component {
}
};
+ lintCode() {
+ const { dataTreePath, dynamicData } = this.props;
+
+ if (!dataTreePath || !this.updateLintingCallback) {
+ return;
+ }
+
+ const errors = _.get(
+ dynamicData,
+ getEvalErrorPath(dataTreePath),
+ [],
+ ) as EvaluationError[];
+
+ let annotations: Annotation[] = [];
+
+ if (this.editor) {
+ annotations = getLintAnnotations(this.editor.getValue(), errors);
+ }
+
+ this.updateLintingCallback(this.editor, annotations);
+ }
+
static updateMarkings = (
editor: CodeMirror.Editor,
marking: Array,
@@ -472,9 +527,11 @@ class CodeEditor extends Component {
getEvalErrorPath(dataTreePath),
[],
) as EvaluationError[];
+
const filteredLintErrors = errors.filter(
(error) => error.errorType !== PropertyEvaluationErrorType.LINT,
);
+
const pathEvaluatedValue = _.get(dataTree, getEvalValuePath(dataTreePath));
return {
@@ -515,6 +572,10 @@ class CodeEditor extends Component {
evaluated = pathEvaluatedValue;
}
+ if (getFeatureFlags().LINTING) {
+ this.lintCode();
+ }
+
const showEvaluatedValue =
this.state.isFocused &&
("evaluatedValue" in this.props ||
diff --git a/app/client/src/components/editorComponents/CodeEditor/lintHelpers.test.ts b/app/client/src/components/editorComponents/CodeEditor/lintHelpers.test.ts
new file mode 100644
index 000000000000..e242c59bbe53
--- /dev/null
+++ b/app/client/src/components/editorComponents/CodeEditor/lintHelpers.test.ts
@@ -0,0 +1,161 @@
+import { Severity } from "entities/AppsmithConsole";
+import {
+ EvaluationError,
+ PropertyEvaluationErrorType,
+} from "utils/DynamicBindingUtils";
+import {
+ getKeyPositionInString,
+ getLintAnnotations,
+ getAllWordOccurrences,
+} from "./lintHelpers";
+
+describe("getAllWordOccurences()", function() {
+ it("should get all the indexes", () => {
+ const res = getAllWordOccurrences("this is a `this` string", "this");
+ expect(res).toEqual([0, 11]);
+ });
+
+ it("should return empty array", () => {
+ expect(getAllWordOccurrences("this is a string", "number")).toEqual([]);
+ });
+});
+
+describe("getKeyPositionsInString()", () => {
+ it("should return results for single line string", () => {
+ const res = getKeyPositionInString("this is a `this` string", "this");
+ expect(res).toEqual([
+ { line: 0, ch: 0 },
+ { line: 0, ch: 11 },
+ ]);
+ });
+
+ it("should return results for multiline string", () => {
+ const res = getKeyPositionInString("this is a \n`this` string", "this");
+ expect(res).toEqual([
+ { line: 0, ch: 0 },
+ { line: 1, ch: 1 },
+ ]);
+ });
+});
+
+describe("getLintAnnotations()", () => {
+ const { LINT, PARSE } = PropertyEvaluationErrorType;
+ const { ERROR, WARNING } = Severity;
+ it("should return proper annotations", () => {
+ const value = `Hello {{ world == test }}\n {{text}}`;
+ const errors: EvaluationError[] = [
+ {
+ errorMessage: "Expected '===' and instead saw '=='.",
+ severity: WARNING,
+ raw:
+ "\n function closedFunction () {\n const result = world == test \n return result;\n }\n closedFunction()\n ",
+ errorType: LINT,
+ originalBinding: "world == test ",
+ errorSegment: " const result = world == test ",
+ variables: ["===", "==", null, null],
+ code: "W116",
+ },
+ {
+ errorType: LINT,
+ raw:
+ "\n function closedFunction () {\n const result = world == test \n return result;\n }\n closedFunction()\n ",
+ severity: WARNING,
+ errorMessage: "'world' is not defined.",
+ errorSegment: " const result = world == test ",
+ originalBinding: "world == test ",
+ variables: ["world", null, null, null],
+ code: "W117",
+ },
+ {
+ errorType: LINT,
+ raw:
+ "\n function closedFunction () {\n const result = world == test \n return result;\n }\n closedFunction()\n ",
+ severity: WARNING,
+ errorMessage: "'test' is not defined.",
+ errorSegment: " const result = world == test ",
+ originalBinding: "world == test ",
+ variables: ["test", null, null, null],
+ code: "W117",
+ },
+ {
+ errorType: PARSE,
+ raw:
+ "\n function closedFunction () {\n const result = world == test \n return result;\n }\n closedFunction()\n ",
+ severity: ERROR,
+ errorMessage: "ReferenceError: world is not defined",
+ originalBinding: " world == test ",
+ },
+ {
+ errorMessage: "'text' is not defined.",
+ severity: WARNING,
+ raw:
+ "\n function closedFunction () {\n const result = text\n return result;\n }\n closedFunction()\n ",
+ errorType: LINT,
+ originalBinding: "text",
+ errorSegment: " const result = text",
+ variables: ["text", null, null, null],
+ code: "W117",
+ },
+ {
+ errorMessage: "ReferenceError: text is not defined",
+ severity: WARNING,
+ raw:
+ "\n function closedFunction () {\n const result = text\n return result;\n }\n closedFunction()\n ",
+ errorType: PARSE,
+ originalBinding: "text",
+ },
+ ];
+
+ const res = getLintAnnotations(value, errors);
+ expect(res).toEqual([
+ {
+ from: {
+ line: 0,
+ ch: 15,
+ },
+ to: {
+ line: 0,
+ ch: 17,
+ },
+ message: "Expected '===' and instead saw '=='.",
+ severity: "warning",
+ },
+ {
+ from: {
+ line: 0,
+ ch: 9,
+ },
+ to: {
+ line: 0,
+ ch: 14,
+ },
+ message: "'world' is not defined.",
+ severity: "warning",
+ },
+ {
+ from: {
+ line: 0,
+ ch: 18,
+ },
+ to: {
+ line: 0,
+ ch: 22,
+ },
+ message: "'test' is not defined.",
+ severity: "warning",
+ },
+ {
+ from: {
+ line: 1,
+ ch: 4,
+ },
+ to: {
+ line: 1,
+ ch: 8,
+ },
+ message: "'text' is not defined.",
+ severity: "warning",
+ },
+ ]);
+ });
+});
diff --git a/app/client/src/components/editorComponents/CodeEditor/lintHelpers.ts b/app/client/src/components/editorComponents/CodeEditor/lintHelpers.ts
new file mode 100644
index 000000000000..551248ae93a3
--- /dev/null
+++ b/app/client/src/components/editorComponents/CodeEditor/lintHelpers.ts
@@ -0,0 +1,131 @@
+import _ from "lodash";
+import { Annotation, Position } from "codemirror";
+import {
+ EvaluationError,
+ PropertyEvaluationErrorType,
+} from "utils/DynamicBindingUtils";
+
+export const getIndexOfRegex = (
+ str: string,
+ regex: RegExp,
+ start = 0,
+): number => {
+ const pos = str.substr(start).search(regex);
+ return pos > -1 ? pos + start : pos;
+};
+
+const buildBoundaryRegex = (key: string) => {
+ return key
+ .replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")
+ .replace(/\w+/g, "\\b$&\\b");
+};
+
+export const getAllWordOccurrences = (str: string, key: string) => {
+ const indices = [];
+ let index, startIndex;
+ const regex = new RegExp(buildBoundaryRegex(key));
+ index = getIndexOfRegex(str, regex, startIndex);
+ while (index > -1) {
+ indices.push(index);
+ startIndex = index + key.length;
+ index = getIndexOfRegex(str, regex, startIndex);
+ }
+
+ return indices;
+};
+
+export const getKeyPositionInString = (
+ str: string,
+ key: string,
+): Position[] => {
+ const indices = getAllWordOccurrences(str, key);
+ let positions: Position[] = [];
+ if (str.includes("\n")) {
+ for (const index of indices) {
+ const substr = str.substr(0, index);
+ const substrLines = substr.split("\n");
+ const ch = _.last(substrLines)?.length || 0;
+ const line = substrLines.length - 1;
+
+ positions.push({ line, ch });
+ }
+ } else {
+ positions = indices.map((index) => ({ line: 0, ch: index }));
+ }
+ return positions;
+};
+
+export const getLintAnnotations = (
+ value: string,
+ errors: EvaluationError[],
+): Annotation[] => {
+ const annotations: Annotation[] = [];
+ const lintErrors = errors.filter(
+ (error) => error.errorType === PropertyEvaluationErrorType.LINT,
+ );
+
+ lintErrors.forEach((error) => {
+ const { errorMessage, originalBinding, severity, variables } = error;
+
+ if (!originalBinding) {
+ return annotations;
+ }
+
+ // We find the location of binding in the editor value and then
+ // we find locations of jshint variables (a, b, c, d) in the binding and highlight them
+ const bindingPositions = getKeyPositionInString(value, originalBinding);
+
+ for (const bindingLocation of bindingPositions) {
+ if (variables?.filter((v) => v).length) {
+ for (let variable of variables) {
+ if (typeof variable === "number") {
+ variable = variable.toString();
+ }
+ if (variable && originalBinding.includes(variable)) {
+ const variableLocations = getKeyPositionInString(
+ originalBinding,
+ variable,
+ );
+
+ for (const variableLocation of variableLocations) {
+ const from = {
+ line: bindingLocation.line + variableLocation.line,
+ // if the binding is a multiline function we need to
+ // use jshint variable position as the starting point
+ ch:
+ variableLocation.line > 0
+ ? variableLocation.ch
+ : variableLocation.ch + bindingLocation.ch,
+ };
+
+ const to = {
+ line: from.line,
+ ch: from.ch + variable.length,
+ };
+
+ const annotation = {
+ from,
+ to,
+ message: errorMessage,
+ severity,
+ };
+
+ annotations.push(annotation);
+ }
+ }
+ }
+ } else {
+ const from = bindingLocation;
+ const to = { line: from.line, ch: from.ch + 3 };
+ const annotation = {
+ from,
+ to,
+ message: errorMessage,
+ severity,
+ };
+ annotations.push(annotation);
+ }
+ }
+ });
+ return annotations;
+};
diff --git a/app/client/src/components/editorComponents/Debugger/Errors.tsx b/app/client/src/components/editorComponents/Debugger/Errors.tsx
index 9464249ff48a..e962522ee1fa 100644
--- a/app/client/src/components/editorComponents/Debugger/Errors.tsx
+++ b/app/client/src/components/editorComponents/Debugger/Errors.tsx
@@ -1,12 +1,11 @@
import React, { useEffect } from "react";
import { useSelector } from "react-redux";
import styled from "styled-components";
-import { getDebuggerErrors } from "selectors/debuggerSelectors";
+import { getFilteredErrors } from "selectors/debuggerSelectors";
import LogItem, { getLogItemProps } from "./LogItem";
import { BlankState } from "./helpers";
import { createMessage, NO_ERRORS } from "constants/messages";
import { getCurrentUser } from "selectors/usersSelectors";
-import { AppState } from "reducers";
import { bootIntercom } from "utils/helpers";
const ContainerWrapper = styled.div`
@@ -20,8 +19,7 @@ const ListWrapper = styled.div`
`;
function Errors(props: { hasShortCut?: boolean }) {
- const errors = useSelector(getDebuggerErrors);
- const expandId = useSelector((state: AppState) => state.ui.debugger.expandId);
+ const errors = useSelector(getFilteredErrors);
const currentUser = useSelector(getCurrentUser);
useEffect(() => {
@@ -39,14 +37,9 @@ function Errors(props: { hasShortCut?: boolean }) {
) : (
Object.values(errors).map((e, index) => {
const logItemProps = getLogItemProps(e);
- const id = Object.keys(errors)[index];
-
+ // Expand all errors by default
return (
-
+
);
})
)}
diff --git a/app/client/src/components/editorComponents/Debugger/LogItem.tsx b/app/client/src/components/editorComponents/Debugger/LogItem.tsx
index 2f4ef5c5b977..fdc2bb722424 100644
--- a/app/client/src/components/editorComponents/Debugger/LogItem.tsx
+++ b/app/client/src/components/editorComponents/Debugger/LogItem.tsx
@@ -290,7 +290,7 @@ function LogItem(props: LogItemProps) {
return (
onLogClick(event, e)}
>
{e.message}
diff --git a/app/client/src/components/editorComponents/Debugger/hooks.ts b/app/client/src/components/editorComponents/Debugger/hooks.ts
index 7d29b5d86e86..d3b5b8939df8 100644
--- a/app/client/src/components/editorComponents/Debugger/hooks.ts
+++ b/app/client/src/components/editorComponents/Debugger/hooks.ts
@@ -21,7 +21,7 @@ import {
} from "./helpers";
import history from "utils/history";
import { getSelectedWidget } from "selectors/ui";
-import { getDebuggerErrors } from "selectors/debuggerSelectors";
+import { getFilteredErrors } from "selectors/debuggerSelectors";
import { isEqual, keyBy } from "lodash";
import {
getPluginIcon,
@@ -158,7 +158,7 @@ export const useEntityLink = () => {
export const useGetEntityInfo = (name: string) => {
const entity = useSelector((state: AppState) => state.evaluations.tree[name]);
- const debuggerErrors = useSelector(getDebuggerErrors);
+ const debuggerErrors = useSelector(getFilteredErrors);
const action = useSelector((state: AppState) =>
isAction(entity) ? getAction(state, entity.actionId) : undefined,
);
diff --git a/app/client/src/components/editorComponents/Debugger/index.tsx b/app/client/src/components/editorComponents/Debugger/index.tsx
index 86d987f91418..e1bf67b24192 100644
--- a/app/client/src/components/editorComponents/Debugger/index.tsx
+++ b/app/client/src/components/editorComponents/Debugger/index.tsx
@@ -12,8 +12,9 @@ import { Colors } from "constants/Colors";
import { getTypographyByKey } from "constants/DefaultTheme";
import { Layers } from "constants/Layers";
import { stopEventPropagation } from "utils/AppsmithUtils";
+import { getFilteredErrors } from "selectors/debuggerSelectors";
-const Container = styled.div<{ errorCount: number }>`
+const Container = styled.div<{ errorCount: number; warningCount: number }>`
z-index: ${Layers.debugger};
background-color: ${(props) =>
props.theme.colors.debugger.floatingButton.background};
@@ -40,8 +41,10 @@ const Container = styled.div<{ errorCount: number }>`
height: 16px;
padding: ${(props) => props.theme.spaces[1]}px;
background-color: ${(props) =>
- !!props.errorCount
- ? props.theme.colors.debugger.floatingButton.errorCount
+ props.errorCount + props.warningCount > 0
+ ? props.errorCount === 0
+ ? props.theme.colors.debugger.floatingButton.warningCount
+ : props.theme.colors.debugger.floatingButton.errorCount
: props.theme.colors.debugger.floatingButton.noErrorCount};
border-radius: 10px;
position: absolute;
@@ -55,9 +58,15 @@ const Container = styled.div<{ errorCount: number }>`
function Debugger() {
const dispatch = useDispatch();
- const errorCount = useSelector(
- (state: AppState) => Object.keys(state.ui.debugger.errors).length,
- );
+ const messageCounters = useSelector((state) => {
+ const errorKeys = Object.keys(getFilteredErrors(state));
+ const warnings = errorKeys.filter((key: string) => key.includes("warning"))
+ .length;
+ const errors = errorKeys.length - warnings;
+ return { errors, warnings };
+ });
+
+ const totalMessageCount = messageCounters.errors + messageCounters.warnings;
const showDebugger = useSelector(
(state: AppState) => state.ui.debugger.isOpen,
);
@@ -74,14 +83,19 @@ function Debugger() {
return (
- {errorCount}
+ {!!messageCounters.errors && (
+
+ {totalMessageCount}
+
+ )}
);
- return ;
+ return ;
}
export default Debugger;
diff --git a/app/client/src/components/editorComponents/DropdownComponent.tsx b/app/client/src/components/editorComponents/DropdownComponent.tsx
index d17447c3559d..1df012af1cd2 100644
--- a/app/client/src/components/editorComponents/DropdownComponent.tsx
+++ b/app/client/src/components/editorComponents/DropdownComponent.tsx
@@ -7,10 +7,7 @@ import {
InputGroup,
IMenuProps,
} from "@blueprintjs/core";
-import {
- BaseButton,
- ButtonStyleName,
-} from "components/designSystems/blueprint/ButtonComponent";
+import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import {
ItemRenderer,
Select,
@@ -41,7 +38,6 @@ const StyledMenu = styled(Menu)`
`;
const StyledMenuItem = styled(MenuItem)`
border-radius: 0;
-
&&&.bp3-active {
background: ${(props) => props.theme.colors.propertyPane.activeButtonText};
}
@@ -49,8 +45,11 @@ const StyledMenuItem = styled(MenuItem)`
class DropdownComponent extends Component {
componentDidMount() {
- const { input, selected } = this.props;
- input && input.onChange(selected?.value);
+ const { input, options } = this.props;
+ // set selected option to first option by default
+ if (input && !input.value) {
+ input.onChange(options[0].value);
+ }
}
private newItemTextInput: HTMLInputElement | null = null;
private setNewItemTextInput = (element: HTMLInputElement | null) => {
@@ -85,8 +84,7 @@ class DropdownComponent extends Component {
const displayMode = (
{
const editMode = (
-
+
);
return (
@@ -119,8 +113,9 @@ class DropdownComponent extends Component {
);
};
onItemSelect = (item: DropdownOption): void => {
- this.props.input?.onChange(item.value);
- this.props.selectHandler(item.value);
+ const { input, selectHandler } = this.props;
+ input && input.onChange(item.value);
+ selectHandler && selectHandler(item.value);
};
renderItem: ItemRenderer = (
@@ -141,32 +136,42 @@ class DropdownComponent extends Component {
/>
);
};
+
+ getDropdownOption = (value: string): DropdownOption | undefined => {
+ return this.props.options.find((option) => option.value === value);
+ };
+
getSelectedDisplayText = () => {
- if (this.props.selected) {
- const selectedValue = this.props.selected.value;
- const item: DropdownOption | undefined = this.props.options.find(
- (option) => option.value === selectedValue,
- );
+ const { input, selected } = this.props;
+ if (input) {
+ const item = this.getDropdownOption(input.value);
+ return item && item.label;
+ }
+ if (selected) {
+ const item = this.getDropdownOption(selected.value);
return item && item.label;
}
return "";
};
+ getActiveOption = (): DropdownOption => {
+ const { input, options, selected } = this.props;
+ const defaultActiveOption = options[0];
+
+ if (input) {
+ return this.getDropdownOption(input.value) || defaultActiveOption;
+ } else {
+ return selected || defaultActiveOption;
+ }
+ };
+
render() {
- const {
- accent,
- autocomplete,
- filled,
- input,
- options,
- selected,
- width,
- } = this.props;
+ const { autocomplete, input, options, width } = this.props;
return (
{
{this.props.toggle || (
@@ -196,7 +201,7 @@ class DropdownComponent extends Component {
export interface DropdownComponentProps {
hasLabel?: boolean;
options: DropdownOption[];
- selectHandler: (selectedValue: string) => void;
+ selectHandler?: (selectedValue: string) => void;
selected?: DropdownOption;
multiselectDisplayType?: "TAGS" | "CHECKBOXES";
checked?: boolean;
@@ -207,8 +212,6 @@ export interface DropdownComponentProps {
addItemHandler: (name: string) => void;
};
toggle?: ReactNode;
- accent?: ButtonStyleName;
- filled?: boolean;
input?: WrappedFieldInputProps;
width?: string;
}
diff --git a/app/client/src/components/editorComponents/GlobalSearch/index.tsx b/app/client/src/components/editorComponents/GlobalSearch/index.tsx
index 241219fe0849..30440c34af6e 100644
--- a/app/client/src/components/editorComponents/GlobalSearch/index.tsx
+++ b/app/client/src/components/editorComponents/GlobalSearch/index.tsx
@@ -441,7 +441,11 @@ function GlobalSearch() {
const handleItemLinkClick = (itemArg?: SearchItem, source?: string) => {
const item = itemArg || activeItem;
- const type = getItemType(item) as SEARCH_ITEM_TYPES;
+ const type = getItemType(item);
+
+ // When there is no active item(or no search results) do nothing
+ // on pressing enter
+ if (!searchResults.length) return;
AnalyticsUtil.logEvent("NAVIGATE_TO_ENTITY_FROM_OMNIBAR", {
type,
diff --git a/app/client/src/components/editorComponents/HighlightedCode/themes.ts b/app/client/src/components/editorComponents/HighlightedCode/themes.ts
index 087532058825..dad721c47410 100644
--- a/app/client/src/components/editorComponents/HighlightedCode/themes.ts
+++ b/app/client/src/components/editorComponents/HighlightedCode/themes.ts
@@ -11,7 +11,7 @@ export const LIGHT = css`
text-align: left;
white-space: pre;
word-spacing: normal;
- word-break: normal;
+ word-break: break-word;
word-wrap: normal;
line-height: 1.5;
diff --git a/app/client/src/components/editorComponents/Onboarding/EndTour.tsx b/app/client/src/components/editorComponents/Onboarding/EndTour.tsx
index 374b40530547..53e1258a2957 100644
--- a/app/client/src/components/editorComponents/Onboarding/EndTour.tsx
+++ b/app/client/src/components/editorComponents/Onboarding/EndTour.tsx
@@ -1,6 +1,8 @@
import { endOnboarding } from "actions/onboardingActions";
import React from "react";
import { useDispatch } from "react-redux";
+import { showWelcomeScreen } from "selectors/onboardingSelectors";
+import { useSelector } from "store";
import styled from "styled-components";
import AnalyticsUtil from "utils/AnalyticsUtil";
@@ -13,17 +15,23 @@ const EndTourText = styled.span`
function EndTour() {
const dispatch = useDispatch();
+ const showingWelcomeScreen = useSelector(showWelcomeScreen);
- return (
- {
- AnalyticsUtil.logEvent("END_ONBOARDING");
- dispatch(endOnboarding());
- }}
- >
- End Tour
-
- );
+ // Showing end tour in the header only when the welcome screen is shown
+ if (showingWelcomeScreen) {
+ return (
+ {
+ AnalyticsUtil.logEvent("END_ONBOARDING");
+ dispatch(endOnboarding());
+ }}
+ >
+ End Tour
+
+ );
+ }
+
+ return null;
}
export default EndTour;
diff --git a/app/client/src/components/editorComponents/ResizableComponent.tsx b/app/client/src/components/editorComponents/ResizableComponent.tsx
index 627c0da5a1ce..4d9fc88a421b 100644
--- a/app/client/src/components/editorComponents/ResizableComponent.tsx
+++ b/app/client/src/components/editorComponents/ResizableComponent.tsx
@@ -1,5 +1,5 @@
import React, { useContext, useRef, memo, useMemo } from "react";
-import { XYCoord } from "react-dnd";
+import { XYCord } from "utils/hooks/useCanvasDragging";
import {
WidgetOperations,
@@ -42,9 +42,9 @@ import { getOccupiedSpaces } from "selectors/editorSelectors";
import { commentModeSelector } from "selectors/commentsSelectors";
import { snipingModeSelector } from "selectors/editorSelectors";
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
-import { getParentToOpenIfAny } from "utils/hooks/useClickOpenPropPane";
import { getCanvasWidgets } from "selectors/entitiesSelector";
import { focusWidget } from "actions/widgetActions";
+import { getParentToOpenIfAny } from "utils/hooks/useClickToSelectWidget";
export type ResizableComponentProps = WidgetProps & {
paddingOffset: number;
@@ -132,7 +132,7 @@ export const ResizableComponent = memo(function ResizableComponent(
// Checks if the current resize position has any collisions
// If yes, set isColliding flag to true.
// If no, set isColliding flag to false.
- const isColliding = (newDimensions: UIElementSize, position: XYCoord) => {
+ const isColliding = (newDimensions: UIElementSize, position: XYCord) => {
// Moving the bounding element calculations inside
// to make this expensive operation only whne
const boundingElementClientRect = boundingElement
@@ -231,7 +231,7 @@ export const ResizableComponent = memo(function ResizableComponent(
// 1) There is no collision
// 2) There is a change in widget size
// Update widget, if both of the above are true.
- const updateSize = (newDimensions: UIElementSize, position: XYCoord) => {
+ const updateSize = (newDimensions: UIElementSize, position: XYCord) => {
// Get the difference in size of the widget, before and after resizing.
const delta: UIElementSize = {
height: newDimensions.height - dimensions.height,
@@ -275,12 +275,8 @@ export const ResizableComponent = memo(function ResizableComponent(
selectedWidget !== props.widgetId &&
selectWidget(props.widgetId);
}
-
- // Let the propertypane show.
- // The propertypane decides whether to show itself, based on
- // whether it was showing when the widget resize started.
- showPropertyPane && showPropertyPane(props.widgetId, undefined, true);
-
+ // Property pane closes after a resize/drag
+ showPropertyPane && showPropertyPane();
AnalyticsUtil.logEvent("WIDGET_RESIZE_END", {
widgetName: props.widgetName,
widgetType: props.type,
diff --git a/app/client/src/components/editorComponents/ResizableUtils.tsx b/app/client/src/components/editorComponents/ResizableUtils.tsx
index 0a0fc4cb1f6f..08f908f6a3cf 100644
--- a/app/client/src/components/editorComponents/ResizableUtils.tsx
+++ b/app/client/src/components/editorComponents/ResizableUtils.tsx
@@ -1,4 +1,4 @@
-import { XYCoord } from "react-dnd";
+import { XYCord } from "utils/hooks/useCanvasDragging";
import { WidgetProps, WidgetRowCols } from "widgets/BaseWidget";
import { GridDefaults } from "constants/WidgetConstants";
@@ -8,7 +8,7 @@ export const RESIZABLE_CONTAINER_BORDER_THEME_INDEX = 1;
export const computeRowCols = (
delta: UIElementSize,
- position: XYCoord,
+ position: XYCord,
props: WidgetProps,
) => {
return {
@@ -51,7 +51,7 @@ export const hasRowColsChanged = (
export const computeFinalRowCols = (
delta: UIElementSize,
- position: XYCoord,
+ position: XYCord,
props: WidgetProps,
): WidgetRowCols | false => {
const newRowCols = computeBoundedRowCols(
diff --git a/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx b/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx
index e5bdd6a574aa..2dea41123a64 100644
--- a/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx
+++ b/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx
@@ -17,6 +17,7 @@ import { getIsTableFilterPaneVisible } from "selectors/tableFilterSelectors";
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
import { snipingModeSelector } from "selectors/editorSelectors";
import { bindDataToWidget } from "../../../actions/propertyPaneActions";
+import { hideErrors } from "selectors/debuggerSelectors";
const PositionStyle = styled.div<{ topRow: number; isSnipingMode: boolean }>`
position: absolute;
@@ -77,6 +78,8 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) {
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
+ const shouldHideErrors = useSelector(hideErrors);
+
const isTableFilterPaneVisible = useSelector(getIsTableFilterPaneVisible);
const togglePropertyEditor = (e: any) => {
@@ -133,7 +136,7 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) {
((focusedWidget === props.widgetId || showAsSelected) &&
!isDragging &&
!isResizing) ||
- !!props.errorCount)
+ (!!props.errorCount && !shouldHideErrors))
);
};
@@ -165,7 +168,7 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) {
diff --git a/app/client/src/components/editorComponents/form/fields/DynamicDropdownField.tsx b/app/client/src/components/editorComponents/form/fields/DynamicDropdownField.tsx
index 223a8097c5f7..c0062a81b89d 100644
--- a/app/client/src/components/editorComponents/form/fields/DynamicDropdownField.tsx
+++ b/app/client/src/components/editorComponents/form/fields/DynamicDropdownField.tsx
@@ -1,49 +1,17 @@
import React from "react";
import { Field, BaseFieldProps } from "redux-form";
import DropdownComponent from "components/editorComponents/DropdownComponent";
-import { ButtonStyleName } from "components/designSystems/blueprint/ButtonComponent";
import { DropdownOption } from "widgets/DropdownWidget";
interface DynamicDropdownFieldOptions {
options: DropdownOption[];
- accent?: ButtonStyleName;
- filled?: boolean;
width?: string;
}
type DynamicDropdownFieldProps = BaseFieldProps & DynamicDropdownFieldOptions;
-
-class DynamicDropdownField extends React.Component<
- DynamicDropdownFieldProps,
- {
- selectedOption: DropdownOption;
- }
-> {
- constructor(props: DynamicDropdownFieldProps) {
- super(props);
- this.state = {
- selectedOption: this.props.options[0],
- };
- }
-
- handleOptionSelection = (selectedValue: string): void => {
- const selectedOption = this.props.options.find(
- (option) => option.value === selectedValue,
- ) as DropdownOption;
- this.setState({
- selectedOption,
- });
- };
-
+class DynamicDropdownField extends React.Component {
render() {
- const dropdownProps = {
- selectHandler: this.handleOptionSelection,
- selected: this.state.selectedOption,
- };
-
- return (
-
- );
+ return ;
}
}
diff --git a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx
index 08a78cc4dd75..e95beb0ab3c3 100644
--- a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx
+++ b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx
@@ -306,7 +306,7 @@ class EmbeddedDatasourcePathComponent extends React.Component {
return (
-
+
{displayValue && datasource && !("id" in datasource) ? (
) : datasource && "id" in datasource ? (
diff --git a/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx b/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx
index ea5a768271af..f70ccd556538 100644
--- a/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx
+++ b/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx
@@ -174,8 +174,6 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
{
}
export interface ControlProps extends ControlData, ControlFunctions {
+ serverLabel?: string;
key?: string;
extraData?: ControlData[];
formName: string;
@@ -49,6 +50,7 @@ export interface ControlData {
validationRegex?: string;
dataType?: InputType;
isRequired?: boolean;
+ conditionals: string;
hidden?: HiddenType;
placeholderText?: string;
schema?: any;
diff --git a/app/client/src/components/formControls/DynamicTextFieldControl.test.tsx b/app/client/src/components/formControls/DynamicTextFieldControl.test.tsx
index dd3508467c24..db5850abd727 100644
--- a/app/client/src/components/formControls/DynamicTextFieldControl.test.tsx
+++ b/app/client/src/components/formControls/DynamicTextFieldControl.test.tsx
@@ -7,6 +7,7 @@ import { PluginType } from "entities/Action";
import { waitFor } from "@testing-library/dom";
import userEvent from "@testing-library/user-event";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
+import { DatasourceComponentTypes, UIComponentTypes } from "api/PluginApi";
function TestForm(props: any) {
return {props.children}
;
@@ -55,8 +56,8 @@ describe("DynamicTextFieldControl", () => {
templates: {
CREATE: "test plugin template",
},
- uiComponent: "DbEditorForm",
- datasourceComponent: "AutoForm",
+ uiComponent: UIComponentTypes.DbEditorForm,
+ datasourceComponent: DatasourceComponentTypes.AutoForm,
},
],
},
diff --git a/app/client/src/components/formControls/FilePickerControl.tsx b/app/client/src/components/formControls/FilePickerControl.tsx
index 49a51eecc4fa..2d0bc4c91788 100644
--- a/app/client/src/components/formControls/FilePickerControl.tsx
+++ b/app/client/src/components/formControls/FilePickerControl.tsx
@@ -101,7 +101,8 @@ class FieldFileInput extends React.Component {
{value.name}
{
this.openModal();
}}
diff --git a/app/client/src/components/propertyControls/BorderRadiusOptionsControl.tsx b/app/client/src/components/propertyControls/BorderRadiusOptionsControl.tsx
index 9f1d369fed12..4cd6588b959d 100644
--- a/app/client/src/components/propertyControls/BorderRadiusOptionsControl.tsx
+++ b/app/client/src/components/propertyControls/BorderRadiusOptionsControl.tsx
@@ -5,10 +5,13 @@ import { Button, ButtonGroup, IButtonProps } from "@blueprintjs/core";
import BaseControl, { ControlProps } from "./BaseControl";
import { ControlIcons } from "icons/ControlIcons";
import { ThemeProp } from "components/ads/common";
-import {
- ButtonBorderRadius,
- ButtonBorderRadiusTypes,
-} from "components/designSystems/appsmith/IconButtonComponent";
+
+export enum ButtonBorderRadiusTypes {
+ SHARP = "SHARP",
+ ROUNDED = "ROUNDED",
+ CIRCLE = "CIRCLE",
+}
+export type ButtonBorderRadius = keyof typeof ButtonBorderRadiusTypes;
const StyledButtonGroup = styled(ButtonGroup)`
height: 33px;
@@ -36,6 +39,7 @@ const StyledButton = styled(Button)`
export interface BorderRadiusOptionsControlProps extends ControlProps {
propertyValue: ButtonBorderRadius | undefined;
onChange: (borderRaidus: ButtonBorderRadius) => void;
+ options: any[];
}
class BorderRadiusOptionsControl extends BaseControl<
@@ -50,11 +54,35 @@ class BorderRadiusOptionsControl extends BaseControl<
}
public render() {
- const { propertyValue } = this.props;
+ const { options, propertyValue } = this.props;
return (
- {
+ const active =
+ option === ButtonBorderRadiusTypes.SHARP
+ ? propertyValue === option || propertyValue === undefined
+ : propertyValue === option;
+ const icon =
+ option === ButtonBorderRadiusTypes.SHARP ? (
+
+ ) : option === ButtonBorderRadiusTypes.ROUNDED ? (
+
+ ) : (
+
+ );
+
+ return (
+ this.toggleOption(option)}
+ />
+ );
+ })}
+ {/* }
large
@@ -75,7 +103,7 @@ class BorderRadiusOptionsControl extends BaseControl<
}
large
onClick={() => this.toggleOption(ButtonBorderRadiusTypes.CIRCLE)}
- />
+ /> */}
);
}
diff --git a/app/client/src/components/propertyControls/BoxShadowOptionsControl.tsx b/app/client/src/components/propertyControls/BoxShadowOptionsControl.tsx
index d86d8a8a7118..2f3893e01948 100644
--- a/app/client/src/components/propertyControls/BoxShadowOptionsControl.tsx
+++ b/app/client/src/components/propertyControls/BoxShadowOptionsControl.tsx
@@ -5,10 +5,17 @@ import { Button, ButtonGroup, IButtonProps } from "@blueprintjs/core";
import BaseControl, { ControlProps } from "./BaseControl";
import { ControlIcons } from "icons/ControlIcons";
import { ThemeProp } from "components/ads/common";
-import {
- ButtonBoxShadow,
- ButtonBoxShadowTypes,
-} from "components/designSystems/appsmith/IconButtonComponent";
+
+export enum ButtonBoxShadowTypes {
+ NONE = "NONE",
+ VARIANT1 = "VARIANT1",
+ VARIANT2 = "VARIANT2",
+ VARIANT3 = "VARIANT3",
+ VARIANT4 = "VARIANT4",
+ VARIANT5 = "VARIANT5",
+}
+
+export type ButtonBoxShadow = keyof typeof ButtonBoxShadowTypes;
const StyledButtonGroup = styled(ButtonGroup)`
display: grid !important;
@@ -41,6 +48,57 @@ export interface BoxShadowOptionsControlProps extends ControlProps {
propertyValue: ButtonBoxShadow | undefined;
}
+const buttonConfigs = [
+ {
+ variant: ButtonBoxShadowTypes.NONE,
+ icon: {
+ element: ControlIcons.BOX_SHADOW_NONE,
+ color: "#CACACA",
+ width: 16,
+ },
+ },
+ {
+ variant: ButtonBoxShadowTypes.VARIANT1,
+ icon: {
+ element: ControlIcons.BOX_SHADOW_VARIANT1,
+ height: 32,
+ width: 40,
+ },
+ },
+ {
+ variant: ButtonBoxShadowTypes.VARIANT2,
+ icon: {
+ element: ControlIcons.BOX_SHADOW_VARIANT2,
+ height: 28,
+ width: 36,
+ },
+ },
+ {
+ variant: ButtonBoxShadowTypes.VARIANT3,
+ icon: {
+ element: ControlIcons.BOX_SHADOW_VARIANT3,
+ height: 27,
+ width: 32,
+ },
+ },
+ {
+ variant: ButtonBoxShadowTypes.VARIANT4,
+ icon: {
+ element: ControlIcons.BOX_SHADOW_VARIANT4,
+ height: 26,
+ width: 34,
+ },
+ },
+ {
+ variant: ButtonBoxShadowTypes.VARIANT5,
+ icon: {
+ element: ControlIcons.BOX_SHADOW_VARIANT5,
+ height: 26,
+ width: 34,
+ },
+ },
+];
+
class BoxShadowOptionsControl extends BaseControl<
BoxShadowOptionsControlProps
> {
@@ -57,78 +115,29 @@ class BoxShadowOptionsControl extends BaseControl<
return (
-
- }
- large
- onClick={() => this.toggleOption(ButtonBoxShadowTypes.NONE)}
- />
-
- }
- large
- onClick={() => this.toggleOption(ButtonBoxShadowTypes.VARIANT1)}
- />
-
- }
- large
- onClick={() => this.toggleOption(ButtonBoxShadowTypes.VARIANT2)}
- />
-
- }
- large
- onClick={() => this.toggleOption(ButtonBoxShadowTypes.VARIANT3)}
- />
-
- }
- large
- onClick={() => this.toggleOption(ButtonBoxShadowTypes.VARIANT4)}
- />
- {
+ const active =
+ variant === ButtonBoxShadowTypes.NONE
+ ? propertyValue === variant || propertyValue === undefined
+ : propertyValue === variant;
+
+ return (
+
+ }
+ key={variant}
+ large
+ onClick={() => this.toggleOption(variant)}
/>
- }
- large
- onClick={() => this.toggleOption(ButtonBoxShadowTypes.VARIANT5)}
- />
+ );
+ })}
);
}
diff --git a/app/client/src/components/propertyControls/ButtonBorderRadiusControl.tsx b/app/client/src/components/propertyControls/ButtonBorderRadiusControl.tsx
new file mode 100644
index 000000000000..f49126710085
--- /dev/null
+++ b/app/client/src/components/propertyControls/ButtonBorderRadiusControl.tsx
@@ -0,0 +1,83 @@
+import * as React from "react";
+import styled from "styled-components";
+import { Button, ButtonGroup, IButtonProps } from "@blueprintjs/core";
+
+import BaseControl, { ControlProps } from "./BaseControl";
+import { ControlIcons } from "icons/ControlIcons";
+import { ThemeProp } from "components/ads/common";
+
+export enum ButtonBorderRadiusTypes {
+ SHARP = "SHARP",
+ ROUNDED = "ROUNDED",
+ CIRCLE = "CIRCLE",
+}
+export type ButtonBorderRadius = keyof typeof ButtonBorderRadiusTypes;
+
+const StyledButtonGroup = styled(ButtonGroup)`
+ height: 33px;
+`;
+
+const StyledButton = styled(Button)`
+ border: ${(props) =>
+ props.active ? `1px solid #6A86CE` : `1px solid #A9A7A7`};
+ border-radius: 0;
+ box-shadow: none !important;
+ background-image: none !important;
+ background-color: #ffffff !important;
+ & > div {
+ display: flex;
+ }
+ &.bp3-active {
+ box-shadow: none !important;
+ background-color: #ffffff !important;
+ }
+ &:hover {
+ background-color: #ffffff !important;
+ }
+`;
+
+export interface ButtonBorderRadiusOptionsControlProps extends ControlProps {
+ propertyValue: ButtonBorderRadius | undefined;
+ onChange: (borderRaidus: ButtonBorderRadius) => void;
+}
+
+class ButtonBorderRadiusOptionsControl extends BaseControl<
+ ButtonBorderRadiusOptionsControlProps
+> {
+ constructor(props: ButtonBorderRadiusOptionsControlProps) {
+ super(props);
+ }
+
+ static getControlType() {
+ return "BUTTON_BORDER_RADIUS_OPTIONS";
+ }
+
+ public render() {
+ const { propertyValue } = this.props;
+
+ return (
+
+ }
+ large
+ onClick={() => this.toggleOption(ButtonBorderRadiusTypes.SHARP)}
+ />
+
+ }
+ large
+ onClick={() => this.toggleOption(ButtonBorderRadiusTypes.ROUNDED)}
+ />
+
+ );
+ }
+
+ private toggleOption = (option: ButtonBorderRadius) => {
+ this.updateProperty(this.props.propertyName, option);
+ };
+}
+
+export default ButtonBorderRadiusOptionsControl;
diff --git a/app/client/src/components/propertyControls/IconSelectControl.tsx b/app/client/src/components/propertyControls/IconSelectControl.tsx
index 0c6af591b207..e6964f48696a 100644
--- a/app/client/src/components/propertyControls/IconSelectControl.tsx
+++ b/app/client/src/components/propertyControls/IconSelectControl.tsx
@@ -81,7 +81,7 @@ type IconType = IconName | typeof NONE;
const ICON_NAMES = Object.keys(IconNames).map(
(name: string) => IconNames[name as keyof typeof IconNames],
);
-ICON_NAMES.push(NONE);
+ICON_NAMES.unshift(NONE);
const TypedSelect = Select.ofType();
diff --git a/app/client/src/components/propertyControls/index.ts b/app/client/src/components/propertyControls/index.ts
index c0a034b05b2b..c01cfdf9e3c6 100644
--- a/app/client/src/components/propertyControls/index.ts
+++ b/app/client/src/components/propertyControls/index.ts
@@ -45,8 +45,9 @@ import MultiSwitchControl, {
import MenuItemsControl from "./MenuItemsControl";
import IconSelectControl from "./IconSelectControl";
import IconAlignControl from "./IconAlignControl";
-import BorderRadiusOptionsControl from "./BorderRadiusOptionsControl";
import BoxShadowOptionsControl from "./BoxShadowOptionsControl";
+import BorderRadiusOptionsControl from "./BorderRadiusOptionsControl";
+import ButtonBorderRadiusOptionsControl from "./ButtonBorderRadiusControl";
export const PropertyControls = {
InputTextControl,
@@ -71,8 +72,9 @@ export const PropertyControls = {
MenuItemsControl,
IconSelectControl,
IconAlignControl,
- BorderRadiusOptionsControl,
BoxShadowOptionsControl,
+ BorderRadiusOptionsControl,
+ ButtonBorderRadiusOptionsControl,
};
export type PropertyControlPropsType =
diff --git a/app/client/src/components/stories/Toast.stories.tsx b/app/client/src/components/stories/Toast.stories.tsx
index 47e04c217cda..44656eddfbba 100644
--- a/app/client/src/components/stories/Toast.stories.tsx
+++ b/app/client/src/components/stories/Toast.stories.tsx
@@ -25,16 +25,26 @@ export default {
export function ToastStory(args: ToastProps) {
return (
-