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) => ( -