diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43dc21e16a..74606891bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Thank you for your interest in Appsmith and taking the time to contribute on this project. 🙌 Appsmith is a project by developers for developers and there are a lot of ways you can contribute. -Feel free to propose changes to this document in a pull request. +If you don't know where to start contributing, ask us on our [Discord channel](https://discord.gg/WN8b2W8j). ## Code of conduct @@ -10,7 +10,7 @@ Read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing ## How can I contribute? -There are many ways in which we/one can to contribute to Appsmith. All contributions are highly appreciated. +There are many ways in which you can to contribute to Appsmith. #### 🐛 Report a bug Report all issues through GitHub Issues using the [Report a Bug](https://github.com/appsmithorg/appsmith/issues/new?assignees=Nikhil-Nandagopal&labels=Bug%2C+High&template=---bug-report.md&title=%5BBug%5D) template. @@ -24,4 +24,4 @@ File your feature request through GitHub Issues using the [Feature Request](http In the process of shipping features quickly, we often forget to keep our docs up to date. You can help by suggesting improvements to our documentation or dive right in to our [Contribution Guide](contributions/docs/CONTRIBUTING.md)! #### ⚙️ Close a Bug / Feature issue -We welcome contributions that help make appsmith bug free & improve the experience of our users. Check out our [Code Contribution Guide](contributions/CodeContributionsGuidelines.md) to begin. +We welcome contributions that help make appsmith bug free & improve the experience of our users. You can also find issues tagged [Good First Issues](https://github.com/appsmithorg/appsmith/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22+bug). Check out our [Code Contribution Guide](contributions/CodeContributionsGuidelines.md) to begin. diff --git a/app/client/cypress/fixtures/MultipleWidgetDsl.json b/app/client/cypress/fixtures/MultipleWidgetDsl.json index 9d99175211..255248bdc5 100644 --- a/app/client/cypress/fixtures/MultipleWidgetDsl.json +++ b/app/client/cypress/fixtures/MultipleWidgetDsl.json @@ -1,94 +1,77 @@ { - "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, - "dynamicBindingPathList": [], - "version": 6, - "minHeight": 1292, - "parentColumnSpace": 1, + "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, + "inputType": "TEXT", + "label": "", + "widgetName": "Input1", + "type": "INPUT_WIDGET", + "isLoading": false, + "parentColumnSpace": 74, + "parentRowSpace": 40, + "leftColumn": 6, + "rightColumn": 11, + "topRow": 13, + "bottomRow": 14, + "parentId": "0", + "widgetId": "1bek8n8byg" + }, + { + "isVisible": true, + "label": "", + "selectionType": "SINGLE_SELECT", + "options": "", + "widgetName": "Dropdown1", + "defaultOptionValue": "VEG", + "type": "DROP_DOWN_WIDGET", + "isLoading": false, + "parentColumnSpace": 74, + "parentRowSpace": 40, "leftColumn": 0, - "children": [ - { - "isVisible": true, - "inputType": "TEXT", - "label": "", - "widgetName": "Input1", - "type": "INPUT_WIDGET", - "isLoading": false, - "parentColumnSpace": 74, - "parentRowSpace": 40, - "leftColumn": 4, - "rightColumn": 9, - "topRow": 0, - "bottomRow": 1, - "parentId": "0", - "widgetId": "orkla1pg88" - }, - { - "isVisible": true, - "label": "", - "selectionType": "SINGLE_SELECT", - "options": "[\n {\n \"label\": \"Vegetarian\",\n \"value\": \"VEG\"\n },\n {\n \"label\": \"Non-Vegetarian\",\n \"value\": \"NON_VEG\"\n },\n {\n \"label\": \"Vegan\",\n \"value\": \"VEGAN\"\n }\n]", - "widgetName": "Dropdown1", - "defaultOptionValue": "VEG", - "type": "DROP_DOWN_WIDGET", - "isLoading": false, - "parentColumnSpace": 74, - "parentRowSpace": 40, - "leftColumn": 4, - "rightColumn": 9, - "topRow": 2, - "bottomRow": 3, - "parentId": "0", - "widgetId": "9iofg44qjm", - "dynamicBindingPathList": [] - }, - { - "isVisible": true, - "defaultText": "This is the initial content of the editor", - "isDisabled": false, - "widgetName": "RichTextEditor1", - "isDefaultClickDisabled": true, - "type": "RICH_TEXT_EDITOR_WIDGET", - "isLoading": false, - "parentColumnSpace": 74, - "parentRowSpace": 40, - "leftColumn": 2, - "rightColumn": 10, - "topRow": 4, - "bottomRow": 9, - "parentId": "0", - "widgetId": "p4uowm5ds3" - }, - { - "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]", - "type": "TABLE_WIDGET", - "isLoading": false, - "parentColumnSpace": 74, - "parentRowSpace": 40, - "leftColumn": 3, - "rightColumn": 11, - "topRow": 12, - "bottomRow": 19, - "parentId": "0", - "widgetId": "iu9vqkj1rd", - "dynamicBindingPathList": [] - } - ] - } -} + "rightColumn": 5, + "topRow": 15, + "bottomRow": 16, + "parentId": "0", + "widgetId": "zd6jycngj7", + "dynamicBindingPathList": [] + }, + { + "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]", + "type": "TABLE_WIDGET", + "isLoading": false, + "parentColumnSpace": 74, + "parentRowSpace": 40, + "leftColumn": 4, + "rightColumn": 12, + "topRow": 19, + "bottomRow": 26, + "parentId": "0", + "widgetId": "ei38nqop3s", + "dynamicBindingPathList": [] + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/datasources.json b/app/client/cypress/fixtures/datasources.json index c9bb0e0069..d53c2abdb9 100644 --- a/app/client/cypress/fixtures/datasources.json +++ b/app/client/cypress/fixtures/datasources.json @@ -5,7 +5,6 @@ "mongo-username": "cypress-test", "mongo-password": "RaopEmky505xYV4p", "mongo-authenticationAuthtype": "SCRAM-SHA-1", - "mongo-sslAuthtype": "No SSL", "postgres-host": "postgres-test-db.cz8diybf9wdj.ap-south-1.rds.amazonaws.com", "postgres-port": 5432, "postgres-databaseName": "fakeapi", diff --git a/app/client/cypress/fixtures/testdata.json b/app/client/cypress/fixtures/testdata.json index 6bc87d181f..2e2e64cf10 100644 --- a/app/client/cypress/fixtures/testdata.json +++ b/app/client/cypress/fixtures/testdata.json @@ -51,7 +51,7 @@ "value": "VEG" }, { - "label": "{{Table1.tableData[1].email}}", + "label": "{{Table1.tableData[2].email}}", "value": "NONVEG" } ], diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/InputWidgets_NavigateTo_validation_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/InputWidgets_NavigateTo_validation_spec.js index d7d81832b6..cba595adff 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/InputWidgets_NavigateTo_validation_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/InputWidgets_NavigateTo_validation_spec.js @@ -17,7 +17,9 @@ describe("Binding the multiple Widgets and validating NavigateTo Page", function it("Input widget test with default value from table widget", function() { cy.openPropertyPane("inputwidget"); cy.get(widgetsPage.defaultInput).type(testdata.defaultInputWidget); - cy.get(widgetsPage.actionSelect).click(); + cy.get(widgetsPage.actionSelect) + .first() + .click(); cy.get(commonlocators.chooseAction) .children() .contains("Navigate To") diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/Widget_loading_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/Widget_loading_spec.js index 1647244a8a..6e36956f58 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/Widget_loading_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/Widget_loading_spec.js @@ -1,88 +1,88 @@ -// const commonlocators = require("../../../locators/commonlocators.json"); -// const formWidgetsPage = require("../../../locators/FormWidgets.json"); -// const dsl = require("../../../fixtures/rundsl.json"); -// const pages = require("../../../locators/Pages.json"); -// const widgetsPage = require("../../../locators/Widgets.json"); -// const publish = require("../../../locators/publishWidgetspage.json"); -// const queryLocators = require("../../../locators/QueryEditor.json"); -// const datasource = require("../../../locators/DatasourcesEditor.json"); -// const apiwidget = require("../../../locators/apiWidgetslocator.json"); -// const testdata = require("../../../fixtures/testdata.json"); +const commonlocators = require("../../../locators/commonlocators.json"); +const formWidgetsPage = require("../../../locators/FormWidgets.json"); +const dsl = require("../../../fixtures/rundsl.json"); +const pages = require("../../../locators/Pages.json"); +const widgetsPage = require("../../../locators/Widgets.json"); +const publish = require("../../../locators/publishWidgetspage.json"); +const queryLocators = require("../../../locators/QueryEditor.json"); +const datasource = require("../../../locators/DatasourcesEditor.json"); +const apiwidget = require("../../../locators/apiWidgetslocator.json"); +const testdata = require("../../../fixtures/testdata.json"); -// const pageid = "MyPage"; -// let updatedName; -// let datasourceName; +const pageid = "MyPage"; +let updatedName; +let datasourceName; -// describe("Binding the multiple widgets and validating default data", function() { -// before(() => { -// cy.addDsl(dsl); -// }); -// it("Create a postgres datasource", function() { -// cy.NavigateToDatasourceEditor(); -// cy.get(datasource.PostgreSQL).click(); +describe("Binding the multiple widgets and validating default data", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Create a postgres datasource", function() { + cy.NavigateToDatasourceEditor(); + cy.get(datasource.PostgreSQL).click(); -// cy.getPluginFormsAndCreateDatasource(); + cy.getPluginFormsAndCreateDatasource(); -// cy.fillPostgresDatasourceForm(); + cy.fillPostgresDatasourceForm(); -// cy.testSaveDatasource(); + cy.testSaveDatasource(); -// cy.get("@createDatasource").then(httpResponse => { -// datasourceName = httpResponse.response.body.data.name; -// }); -// }); -// it("Create and runs query", () => { -// cy.NavigateToQueryEditor(); -// cy.contains(".t--datasource-name", datasourceName) -// .find(queryLocators.createQuery) -// .click(); + cy.get("@createDatasource").then((httpResponse) => { + datasourceName = httpResponse.response.body.data.name; + }); + }); + it("Create and runs query", () => { + cy.NavigateToQueryEditor(); + cy.contains(".t--datasource-name", datasourceName) + .find(queryLocators.createQuery) + .click(); -// cy.get(queryLocators.templateMenu).click(); -// cy.get(".CodeMirror textarea") -// .first() -// .focus() -// .type("select * from users limit 10"); + cy.get(queryLocators.templateMenu).click(); + cy.get(".CodeMirror textarea") + .first() + .focus() + .type("select * from users limit 10"); -// cy.EvaluateCurrentValue("select * from users limit 10"); -// cy.runQuery(); -// }); + cy.EvaluateCurrentValue("select * from users limit 10"); + cy.runQuery(); + }); -// it("Button widget test with on action query run", function() { -// cy.SearchEntityandOpen("Button1"); -// cy.executeDbQuery("Query1"); -// cy.get(commonlocators.editPropCrossButton).click(); -// cy.wait("@updateLayout").should( -// "have.nested.property", -// "response.body.responseMeta.status", -// 200, -// ); -// }); + it("Button widget test with on action query run", function() { + cy.SearchEntityandOpen("Button1"); + cy.executeDbQuery("Query1"); + cy.get(commonlocators.editPropCrossButton).click(); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + }); -// it("Input widget test with default value update with query data", function() { -// cy.SearchEntityandOpen("Input1"); -// cy.get(widgetsPage.defaultInput).type(testdata.defaultInputQuery); -// cy.get(commonlocators.editPropCrossButton).click(); -// cy.wait("@updateLayout").should( -// "have.nested.property", -// "response.body.responseMeta.status", -// 200, -// ); -// }); + it("Input widget test with default value update with query data", function() { + cy.SearchEntityandOpen("Input1"); + cy.get(widgetsPage.defaultInput).type(testdata.defaultInputQuery); + cy.get(commonlocators.editPropCrossButton).click(); + cy.wait("@updateLayout").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + }); -// it("Publish App and validate loading functionalty", function() { -// cy.PublishtheApp(); -// cy.wait(2000); -// cy.get(widgetsPage.widgetBtn) -// .first() -// .click({ force: true }); -// cy.wait("@postExecute").should( -// "have.nested.property", -// "response.body.responseMeta.status", -// 200, -// ); -// cy.get(publish.inputWidget + " " + "input") -// .first() -// .invoke("attr", "value") -// .should("contain", "7"); -// }); -// }); + it("Publish App and validate loading functionalty", function() { + cy.PublishtheApp(); + cy.wait(2000); + cy.get(widgetsPage.widgetBtn) + .first() + .click({ force: true }); + cy.wait("@postExecute").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); + cy.get(publish.inputWidget + " " + "input") + .first() + .invoke("attr", "value") + .should("contain", "7"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/Binding/Widgets_Default_data_validation_spec.js b/app/client/cypress/integration/Smoke_TestSuite/Binding/Widgets_Default_data_validation_spec.js index 37ddc1732b..d98eafe74f 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/Binding/Widgets_Default_data_validation_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/Binding/Widgets_Default_data_validation_spec.js @@ -22,8 +22,7 @@ describe("Binding the multiple widgets and validating default data", function() ); }); - /* - To be enabled once the single select multi select issues are resolved + //To be enabled once the single select multi select issues are resolved it("Dropdown widget test with default value from table widget", function() { cy.openPropertyPane("dropdownwidget"); cy.testJsontext("options", JSON.stringify(testdata.deafultDropDownWidget)); @@ -34,11 +33,10 @@ describe("Binding the multiple widgets and validating default data", function() 200, ); }); -*/ it("validation of default data displayed in all widgets based on row selected", function() { cy.isSelectRow(1); - cy.readTabledataPublish("1", "0").then(tabData => { + cy.readTabledataPublish("1", "0").then((tabData) => { const tabValue = tabData; expect(tabValue).to.be.equal("2736212"); cy.log("the value is" + tabValue); @@ -48,18 +46,17 @@ describe("Binding the multiple widgets and validating default data", function() .invoke("attr", "value") .should("contain", tabValue); }); - /* - cy.readTabledataPublish("1", "1").then(tabData => { + + cy.readTabledataPublish("1", "1").then((tabData) => { const tabValue = tabData; expect(tabValue).to.be.equal("lindsay.ferguson@reqres.in"); cy.log("the value is" + tabValue); cy.get(widgetsPage.defaultSingleSelectValue) .invoke("text") - .then(text => { + .then((text) => { const someText = text; expect(someText).to.equal(tabValue); }); }); - */ }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FilePicker_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FilePicker_spec.js index 08a8e52607..ddd231e396 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FilePicker_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FilePicker_spec.js @@ -6,30 +6,37 @@ describe("FilePicker Widget Functionality", function() { beforeEach(() => { cy.addDsl(dsl); }); + + it("Create API to be used in Filepicker", function() { + cy.log("Login Successful"); + cy.NavigateToAPI_Panel(); + cy.log("Navigation to API Panel screen successful"); + cy.CreateAPI("FirstAPI"); + cy.log("Creation of FirstAPI Action successful"); + cy.enterDatasourceAndPath( + this.data.paginationUrl, + this.data.paginationParam, + ); + cy.SaveAndRunAPI(); + }); + it("FilePicker Widget Functionality", function() { - cy.openPropertyPane("filepickerwidget"); - + cy.SearchEntityandOpen("FilePicker1"); + cy.wait(1000); //Checking the edit props for FilePicker and also the properties of FilePicker widget - cy.testCodeMirror("Upload Files"); - cy.get(commonlocators.editPropCrossButton).click(); }); it("It checks the loading state of filepicker on call the action", function() { - cy.openPropertyPane("filepickerwidget"); + cy.SearchEntityandOpen("FilePicker1"); const fixturePath = "testFile.mov"; - cy.getAlert(commonlocators.filePickerOnFilesSelected); + cy.addAPIFromLightningMenu("FirstAPI"); cy.get(commonlocators.filePickerButton).click(); cy.get(commonlocators.filePickerInput) .first() .attachFile(fixturePath); cy.get(commonlocators.filePickerUploadButton).click(); cy.get(".bp3-spinner").should("have.length", 1); - cy.wait("@updateLayout").should( - "have.nested.property", - "response.body.responseMeta.status", - 200, - ); cy.wait(500); cy.get("button").contains("1 files selected"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/Orgname_validation_spec.js b/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/Orgname_validation_spec.js new file mode 100644 index 0000000000..98871c03ad --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/Orgname_validation_spec.js @@ -0,0 +1,30 @@ +/// + +const homePage = require("../../../locators/HomePage.json"); + +describe("Org name validation spec", function() { + it("create org with leading space validation", function() { + cy.NavigateToHome(); + cy.get(homePage.createOrg) + .should("be.visible") + .first() + .click({ force: true }); + cy.xpath(homePage.inputOrgName) + .should("be.visible") + .type(" "); + cy.get(homePage.submit).should("be.disabled"); + cy.xpath(homePage.cancelBtn).click(); + }); + + it("create org with special characters validation", function() { + cy.get(homePage.createOrg) + .should("be.visible") + .first() + .click({ force: true }); + cy.xpath(homePage.inputOrgName) + .should("be.visible") + .type("Test & Org"); + cy.get(homePage.submit).should("be.enabled"); + cy.xpath(homePage.cancelBtn).click(); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ProductUpdates/ProductUpdates_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ProductUpdates/ProductUpdates_spec.js new file mode 100644 index 0000000000..1bab141c98 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ProductUpdates/ProductUpdates_spec.js @@ -0,0 +1,29 @@ +const commonlocators = require("../../../locators/commonlocators.json"); + +describe("Check for product updates button and modal", function() { + it("Check if we should show the product updates button and it opens the updates modal", function() { + cy.get(commonlocators.homeIcon).click({ force: true }); + cy.wait(2000); + + cy.window() + .its("store") + .invoke("getState") + .then((state) => { + const { releaseItems, newReleasesCount } = state.ui.releases; + if (Array.isArray(releaseItems) && releaseItems.length > 0) { + cy.get("[data-cy=t--product-updates-btn]") + .contains(newReleasesCount) + .click({ force: true }); + cy.wait(500); // modal transition + cy.get(".bp3-dialog-container").contains("Product Updates"); + cy.get("[data-cy=t--product-updates-close-btn]").click({ + force: true, + }); + cy.wait(500); // modal transition + cy.get(".bp3-dialog-container").should("not.exist"); + } else { + cy.get("[data-cy=t--product-updates-btn]").should("not.exist"); + } + }); + }); +}); diff --git a/app/client/cypress/locators/DatasourcesEditor.json b/app/client/cypress/locators/DatasourcesEditor.json index 441a498e0e..12a49f29b5 100644 --- a/app/client/cypress/locators/DatasourcesEditor.json +++ b/app/client/cypress/locators/DatasourcesEditor.json @@ -6,13 +6,11 @@ "username": "input[name='datasourceConfiguration.authentication.username']", "password": "input[name='datasourceConfiguration.authentication.password']", "authenticationAuthtype": "[data-cy=datasourceConfiguration\\.authentication\\.authType]", - "sslAuthtype": "[data-cy=datasourceConfiguration\\.connection\\.ssl\\.authType]", "url": "input[name='datasourceConfiguration.url']", "MongoDB": ".t--plugin-name:contains('MongoDB')", "RESTAPI": ".t--plugin-name:contains('REST API')", "PostgreSQL": ".t--plugin-name:contains('PostgreSQL')", "sectionAuthentication": "[data-cy=section-Authentication]", - "sectionSSL": "[data-cy=section-SSL\\ \\(optional\\)]", "PostgresEntity": ".t--entity-name:contains(PostgreSQL)", "createQuerty": ".t--create-query", "editDatasource": ".t--edit-datasource", diff --git a/app/client/cypress/locators/HomePage.json b/app/client/cypress/locators/HomePage.json index 6b9366e204..ba279d11fc 100644 --- a/app/client/cypress/locators/HomePage.json +++ b/app/client/cypress/locators/HomePage.json @@ -67,5 +67,7 @@ "uploadLogo": "//div/form/input", "removeLogo": ".remove-button a span", "generalTab": "//li//span[text()='General']", - "membersTab": "//li//span[text()='Members']" + "membersTab": "//li//span[text()='Members']", + "cancelBtn": "//span[text()='Cancel']", + "submit": "button:contains('Submit')" } diff --git a/app/client/cypress/manual_TestSuite/Clipboard_Copy_Spec.js b/app/client/cypress/manual_TestSuite/Clipboard_Copy_Spec.js new file mode 100644 index 0000000000..b2b30e8444 --- /dev/null +++ b/app/client/cypress/manual_TestSuite/Clipboard_Copy_Spec.js @@ -0,0 +1,51 @@ +const dsl = require("../../../fixtures/tableWidgetDsl.json"); + + +describe("Test for Clipboard Copy", function() { + it(" Clipboard copy on selecting a row ", function() + { + // Add a table widget + // Click on the Property Pane + // Naviagte to Action Items + // Click on "onRow Selection" Dropdown + // Select "Copy to Clipboard" + // Add Text to be copied + // Select a Row from the table + // Add an Input Widget + // Now paste the copied text + // Ensure the text the same as written + } + ) + it(" Clipboard copy by adding an action button", function() + { + // Add a table widget + // Click on the Property Pane + // Naviagte to Action Items + // Click on "Add button" + // Click on the dropdown + // Select on Copy to Clipboard + // Add a Text + // Click on the Action Button + // Add Input Widget + // Paste the text into the widget + // Ensure the text the same as written + } + ) + it(" Clipboard copy function by converting it to JS ", function() + { + // Add a table widget + // Click on the Property Pane + // Naviagte to Action Items + // Click on "Add button" + // Click on the dropdown + // Click on the Js Option + // Add Copy to Clipboard FUNTION + // Add a Text + // Click on the Action Button + // Add Input Widget + // Paste the text into the widget + // Ensure the text the same as written + } + ) +} +) diff --git a/app/client/cypress/manual_TestSuite/Duplicate_App.js b/app/client/cypress/manual_TestSuite/Duplicate_App.js deleted file mode 100644 index 8b70a6bbb3..0000000000 --- a/app/client/cypress/manual_TestSuite/Duplicate_App.js +++ /dev/null @@ -1,11 +0,0 @@ -const homePage = require("../../../locators/HomePage.json"); - -describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() { - it("Duplicating an application", function() { - // Navigate to home Page - // Click on any application action icon (Three dots) - // Click on "Duplicate" option - // Ensure the application gets copied - // Ensure the name is appended with the word "Copy" - }); -}); diff --git a/app/client/cypress/manual_TestSuite/Duplicate_App_Spec.js b/app/client/cypress/manual_TestSuite/Duplicate_App_Spec.js index 1b6eb8240a..322eff4e4e 100644 --- a/app/client/cypress/manual_TestSuite/Duplicate_App_Spec.js +++ b/app/client/cypress/manual_TestSuite/Duplicate_App_Spec.js @@ -1,14 +1,17 @@ const homePage = require("../../../locators/HomePage.json"); describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() { - it("Duplicating an application", function() { + it("Duplicating an application", function() + { // Navigate to home Page // Click on any application action icon (Three dots) // Click on "Duplicate" option // Ensure the application gets copied // Ensure the name is appended with the word "Copy" - }); - it("Deleting the duplicated Application ", function() { + } + ) + it("Deleting the duplicated Application ", function() + { // Navigate to home Page // Click on any application action icon (Three dots) // Click on "Duplicate" option @@ -18,5 +21,38 @@ describe("Duplicate an application must duplicate every API ,Query widget and Da // Click on Delete option // Click on "Are You Sure?" option // Ensure the App gets deleted - }); -}); + } + ) + + it(" Ensure only the original application is deleted and copy of it exists", function() + { + // Navigate to home Page + // Create an Application + // Add a name to the application + // Navigate to home page + // Now click on the action (Three Dots) + // Select "Duplicate" option + // Ensure App is created with App name prefixed with Copy + // Click on Delete option of Original Application + // Click on "Are You Sure?" option + // Ensure only Original Application is deleted and not the child application + } + ) + + it(" Ensure only the Duplicate application is deleted and original Application of it exists", function() + { + // Navigate to home Page + // Create an Application + // Add a name to the application + // Navigate to home page + // Now click on the action (Three Dots) + // Select "Duplicate" option + // Ensure App is created with App name prefixed with Copy + // Click on Delete option of Duplicate Application + // Click on "Are You Sure?" option + // Ensure only Duplicate Application is deleted and not the Orginal application + } + ) + +} +) diff --git a/app/client/cypress/manual_TestSuite/Invite_flow_Spec.js b/app/client/cypress/manual_TestSuite/Invite_flow_Spec.js new file mode 100644 index 0000000000..380addd9fe --- /dev/null +++ b/app/client/cypress/manual_TestSuite/Invite_flow_Spec.js @@ -0,0 +1,61 @@ +const homePage = require("../../../locators/HomePage.json"); + + +describe("adding role without Email Id", function() { + it("Empty Email ID Invite flow", function() + { + // Navigate to Home Page + // Click on "Share" option + // Add Role from the dropdown + // Ensure the "Invite" option is "Inactive" + } + ) + it("Error message must be dispalyed to user on inappropriate Email ID", function() + { + // Navigate to Home Page + // Click on "Share" option + // Add inappropriate Email Id + // Select the "Role" + // Ensure the "Invite" option is "Inactive" and error message is displayed to user + } + ) + it("Clicking on the organisation list the user must be lead to organisation Station ", function() + { + // Navigate to Home Page + // Navigate to Organisation list + // Click on one of the organisation name + // Ensure user is directed to the organisation + } + ) + it("Admin can only assign another Admin ", function() + { + // Navigate to Organisation Setting + // Navigate to Members + // Navigate to roles + // Ensure your also an "Admin" + // Change the role "Admin" + + } + ) + it("Ensure the user can not delete or create an application in the organisation", function() + { + // Navigate to Home page + // Navigate to Members + // Navigate to roles + // Ensure role is "App Viewer" + // Ensure user is not able to delete or add any user for the application + + } + ) + it("Ensure On invaild Email Id the box must get highlighted", function() + { + // Navigate to Home page + // Click on the Share option + // Ensure the pop up opens + // Enter an Invaild Email Id + // and ensure the Email ID box is highlight + + } + ) +} +) \ No newline at end of file diff --git a/app/client/cypress/manual_TestSuite/Login_Spec.js b/app/client/cypress/manual_TestSuite/Login_Spec.js new file mode 100644 index 0000000000..c85a1355ea --- /dev/null +++ b/app/client/cypress/manual_TestSuite/Login_Spec.js @@ -0,0 +1,34 @@ +const onboarding = require("../../../locators/Onboarding.json"); +const explorer = require("../../../locators/explorerlocators.json"); +const homePage = require("../../../locators/HomePage.json"); +const loginPage = require("../../../locators/LoginPage.json"); + +describe("Onboarding flow", function() +{ + it("Onboarding using Google Id ", function() + { + // Navigate to Login Page + // Click on "Sign In with Google" + // Ensure user is navigated to Google Account + // Do select the Google Id + // Ensure user is navigated into the Appsmith + // Click on the icon on the right with single letter (Profile) + // Check the Email Id + // Click on Logout + } + ) + + it("Onboarding using Github ID ", function() + { + // Navigate to Login Page + // Click on "Sign In with Github" + // Ensure user is navigated to Github Account + // Do select the Github Id + // Ensure user is navigated into the Appsmith + // Click on the icon on the right with single letter (Profile) + // Check the Email Id + // Click on Logout + } + ) +} +) \ No newline at end of file diff --git a/app/client/cypress/manual_TestSuite/Org_Logo_Del.js b/app/client/cypress/manual_TestSuite/Org_Logo_Del.js deleted file mode 100644 index 9f90bcf735..0000000000 --- a/app/client/cypress/manual_TestSuite/Org_Logo_Del.js +++ /dev/null @@ -1,19 +0,0 @@ -const homePage = require("../../../locators/HomePage.json"); - - -describe("Deletion of organisational Logo ", function() { - it(" org logo upload ", function() - { - //Click on the dropdown next to organisational Name - // Navigate between tabs - // Naviagte to General Tab - // Add an Organisational Logo - // Wait until it loads - // Switch between Tabs - // Click on the remove Icon - //Ensure the organisational Logo is deleted - } - ) -} -) - diff --git a/app/client/cypress/manual_TestSuite/Org_Logo_Set.js b/app/client/cypress/manual_TestSuite/Org_Logo_Set.js deleted file mode 100644 index 9ef5c824ef..0000000000 --- a/app/client/cypress/manual_TestSuite/Org_Logo_Set.js +++ /dev/null @@ -1,18 +0,0 @@ -const homePage = require("../../../locators/HomePage.json"); - - -describe("insert organisational Logo ", function() { - it(" org logo upload ", function() - { - //Click on the dropdown next to organisational Name - // Navigate between tabs - // Naviagte to General Tab - // Add an Organisational Logo - //Wait until it loads - // Switch between Tabs - // Navigate to General Tab and ensure the logo exsits - //navigate back to Homepage - } - ) -} -) \ No newline at end of file diff --git a/app/client/cypress/manual_TestSuite/Organisation_Name.js b/app/client/cypress/manual_TestSuite/Organisation_Name.js deleted file mode 100644 index 4babed2641..0000000000 --- a/app/client/cypress/manual_TestSuite/Organisation_Name.js +++ /dev/null @@ -1,11 +0,0 @@ -const homePage = require("../../../locators/HomePage.json"); - -describe("Checking for error message on Organisation Name ", function() { - it("Ensure of Inactive Submit button ", function() { - // Navigate to home Page - // Click on Create Organisation - // Type "Space" as first character - // Ensure "Submit" button does not get Active - // Now click on "X" (Close icon) ensure the pop up closes - }); -}); diff --git a/app/client/cypress/manual_TestSuite/Organisation_Name_Spec.js b/app/client/cypress/manual_TestSuite/Organisation_Name_Spec.js index 035ad05bc2..9eed15193e 100644 --- a/app/client/cypress/manual_TestSuite/Organisation_Name_Spec.js +++ b/app/client/cypress/manual_TestSuite/Organisation_Name_Spec.js @@ -43,5 +43,35 @@ describe("Checking for error message on Organisation Name ", function() { // Ensure the application can be created with the same name } ) + + it("User must not be able to add empty organisation name", function() + { + // Navigate to home Page + // Click on the "Create Organisation" button + // Ensure "Organisation Name" field is empty + // Ensure "Submit" is inactive + } + ) + + it("Cancel creating an Organisation when the Organisation name is empty", function() + { + // Navigate to home Page + // Click on the "Create Organisation" button + // Ensure "Organisation Name" field is empty + // Click on "Cancel" option + // Observe the organisation is not created + } + ) + + + it("Cancel creating an Organisation when the Organisation name is dually filled", function() + { + // Navigate to home Page + // Click on the "Create Organisation" button + // Ensure "Organisation Name" field is enterd respectively + // Click on "Cancel" option + // Observe the organisation is not created + } + ) } ) \ No newline at end of file diff --git a/app/client/cypress/manual_TestSuite/Reusing_Name_of_Deleted_App.js b/app/client/cypress/manual_TestSuite/Reusing_Name_of_Deleted_App.js deleted file mode 100644 index 879abc8b3b..0000000000 --- a/app/client/cypress/manual_TestSuite/Reusing_Name_of_Deleted_App.js +++ /dev/null @@ -1,14 +0,0 @@ -const homePage = require("../../../locators/HomePage.json"); - -describe("Reuse the name of the deleted application name inside the same organisation", function() { - it("Reuse the name of the deleted application name ", function() { - // Navigate to home Page - // Create an Application by name "XYZ" - // Add some widgets - // Navigate back to the application - // Delete the Application - // Click on "Create New" option under samee organisation - // Enter the name "XYZ" - // Ensure the application can be created with the same name - }); -}); diff --git a/app/client/cypress/manual_TestSuite/Share_User_Icon.js b/app/client/cypress/manual_TestSuite/Share_User_Icon.js deleted file mode 100644 index dd662a021b..0000000000 --- a/app/client/cypress/manual_TestSuite/Share_User_Icon.js +++ /dev/null @@ -1,17 +0,0 @@ -const homePage = require("../../../locators/HomePage.json"); - - -describe("Shared user icon ", function() { - it(" User Icon is disaplyed to user ", function() - { - // Navigate to home Page - //Click on Share Icon - // Click on Field to add an Email Id - // Click on the Roles field - // Add an role from the Dropdown - // CLick on Invite - //Now observe the icon next to the Share Icon - } - ) -} -) \ No newline at end of file diff --git a/app/client/cypress/manual_TestSuite/Spl_Chracter_Org_Name.js b/app/client/cypress/manual_TestSuite/Spl_Chracter_Org_Name.js deleted file mode 100644 index 39093565c6..0000000000 --- a/app/client/cypress/manual_TestSuite/Spl_Chracter_Org_Name.js +++ /dev/null @@ -1,11 +0,0 @@ -const homePage = require("../../../locators/HomePage.json"); - -describe("Adding Special Character ", function() { - it("Adding Special Character ", function() { - // Navigate to home Page - // Click on Create Organisation - // Add special as first character - // Ensure "Submit" get Active - // Now click outside and ensure the pop up closes - }); -}); diff --git a/app/client/cypress/manual_TestSuite/Table_Filter_Test_spec.js b/app/client/cypress/manual_TestSuite/Table_Filter_Test_spec.js deleted file mode 100644 index 8c4e5447ce..0000000000 --- a/app/client/cypress/manual_TestSuite/Table_Filter_Test_spec.js +++ /dev/null @@ -1,15 +0,0 @@ -const dsl = require("../../../fixtures/tableWidgetDsl.json"); - - -describe("Test for Table Filter ", function() { - it("Table Filter", function() - { - //Add a table - // click on the column action item - // Click on Select a datatype - // Click on Filter option - // ensure to add filter - } - ) -} -) \ No newline at end of file diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 3b2f4845dd..4629a4fe92 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1262,7 +1262,9 @@ Cypress.Commands.add("togglebarDisable", (value) => { }); Cypress.Commands.add("getAlert", (alertcss) => { - cy.get(commonlocators.dropdownSelectButton).click({ force: true }); + cy.get(commonlocators.dropdownSelectButton) + .first() + .click({ force: true }); cy.get(widgetsPage.menubar) .contains("Show Message") .click({ force: true }) @@ -1279,6 +1281,19 @@ Cypress.Commands.add("getAlert", (alertcss) => { .click({ force: true }); }); +Cypress.Commands.add("addAPIFromLightningMenu", (ApiName) => { + cy.get(commonlocators.dropdownSelectButton) + .click({ force: true }) + .get("ul.bp3-menu") + .children() + .contains("Call An API") + .click({ force: true }) + .get("ul.bp3-menu") + .children() + .contains(ApiName) + .click({ force: true }); +}); + Cypress.Commands.add("radioInput", (index, text) => { cy.get(widgetsPage.RadioInput) .eq(index) @@ -1424,17 +1439,10 @@ Cypress.Commands.add("fillMongoDatasourceForm", () => { cy.get(datasourceEditor["password"]).type( datasourceFormData["mongo-password"], ); - - cy.get(datasourceEditor.sectionSSL).click(); cy.get(datasourceEditor["authenticationAuthtype"]).click(); cy.contains(datasourceFormData["mongo-authenticationAuthtype"]).click({ force: true, }); - - cy.get(datasourceEditor["sslAuthtype"]).click(); - cy.contains(datasourceFormData["mongo-sslAuthtype"]).click({ - force: true, - }); }); Cypress.Commands.add("fillPostgresDatasourceForm", () => { diff --git a/app/client/src/actions/controlActions.tsx b/app/client/src/actions/controlActions.tsx index c6be484c6d..010610bfb1 100644 --- a/app/client/src/actions/controlActions.tsx +++ b/app/client/src/actions/controlActions.tsx @@ -4,7 +4,7 @@ import { BatchAction, batchAction } from "actions/batchActions"; export const updateWidgetPropertyRequest = ( widgetId: string, - propertyName: string, + propertyPath: string, propertyValue: any, renderMode: RenderMode, ): ReduxAction => { @@ -12,7 +12,7 @@ export const updateWidgetPropertyRequest = ( type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY_REQUEST, payload: { widgetId, - propertyName, + propertyPath, propertyValue, renderMode, }, @@ -21,29 +21,49 @@ export const updateWidgetPropertyRequest = ( export const updateWidgetProperty = ( widgetId: string, - propertyName: string, - propertyValue: any, + updates: Record, ): BatchAction => { return batchAction({ type: ReduxActionTypes.UPDATE_WIDGET_PROPERTY, payload: { widgetId, - propertyName, - propertyValue, + updates, }, }); }; +export const batchUpdateWidgetProperty = ( + widgetId: string, + updates: Record, +): ReduxAction => ({ + type: ReduxActionTypes.BATCH_UPDATE_WIDGET_PROPERTY, + payload: { + widgetId, + updates, + }, +}); + +export const deleteWidgetProperty = ( + widgetId: string, + propertyPath: string, +): ReduxAction => ({ + type: ReduxActionTypes.DELETE_WIDGET_PROPERTY, + payload: { + widgetId, + propertyPath, + }, +}); + export const setWidgetDynamicProperty = ( widgetId: string, - propertyName: string, + propertyPath: string, isDynamic: boolean, ): ReduxAction => { return { type: ReduxActionTypes.SET_WIDGET_DYNAMIC_PROPERTY, payload: { widgetId, - propertyName, + propertyPath, isDynamic, }, }; @@ -51,19 +71,23 @@ export const setWidgetDynamicProperty = ( export interface UpdateWidgetPropertyRequestPayload { widgetId: string; - propertyName: string; + propertyPath: string; propertyValue: any; renderMode: RenderMode; } export interface UpdateWidgetPropertyPayload { widgetId: string; - propertyName: string; - propertyValue: any; + updates: Record; } export interface SetWidgetDynamicPropertyPayload { widgetId: string; - propertyName: string; + propertyPath: string; isDynamic: boolean; } + +export interface DeleteWidgetPropertyPayload { + widgetId: string; + propertyPath: string; +} diff --git a/app/client/src/actions/pageActions.tsx b/app/client/src/actions/pageActions.tsx index b18509dde1..28d128e9f0 100644 --- a/app/client/src/actions/pageActions.tsx +++ b/app/client/src/actions/pageActions.tsx @@ -77,11 +77,11 @@ export const updateCurrentPage = (id: string) => ({ payload: { id }, }); -export const updateCanvas = ( +export const initCanvasLayout = ( payload: UpdateCanvasPayload, ): ReduxAction => { return { - type: ReduxActionTypes.UPDATE_CANVAS, + type: ReduxActionTypes.INIT_CANVAS_LAYOUT, payload, }; }; diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index bc7a280b87..6f56308aca 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -5,7 +5,7 @@ import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS, } from "constants/ApiConstants"; import axios, { AxiosPromise, CancelTokenSource } from "axios"; -import { Action } from "entities/Action"; +import { Action, ActionViewMode } from "entities/Action"; export interface CreateActionRequest extends APIRequest { datasourceId: string; @@ -127,7 +127,7 @@ class ActionAPI extends API { static fetchActionsForViewMode( applicationId: string, - ): AxiosPromise> { + ): AxiosPromise> { return API.get(`${ActionAPI.url}/view`, { applicationId }); } diff --git a/app/client/src/assets/images/Github_inverted.png b/app/client/src/assets/images/Github_inverted.png new file mode 100644 index 0000000000..2acb24bb54 Binary files /dev/null and b/app/client/src/assets/images/Github_inverted.png differ diff --git a/app/client/src/assets/images/invalid-page.png b/app/client/src/assets/images/invalid-page.png new file mode 100644 index 0000000000..83e780d2c6 Binary files /dev/null and b/app/client/src/assets/images/invalid-page.png differ diff --git a/app/client/src/components/ads/Button.tsx b/app/client/src/components/ads/Button.tsx index c71f261a27..c49e083978 100644 --- a/app/client/src/components/ads/Button.tsx +++ b/app/client/src/components/ads/Button.tsx @@ -58,6 +58,7 @@ type ButtonProps = CommonComponentProps & { fill?: boolean; href?: string; tag?: "a" | "button"; + type?: "submit" | "reset" | "button"; }; const stateStyles = ( diff --git a/app/client/src/components/ads/EditableText.tsx b/app/client/src/components/ads/EditableText.tsx index 6afa528566..6346f67c16 100644 --- a/app/client/src/components/ads/EditableText.tsx +++ b/app/client/src/components/ads/EditableText.tsx @@ -269,13 +269,15 @@ export const EditableText = (props: EditableTextProps) => { onCancel={onConfirm} /> - - {savingState === SavingState.STARTED ? ( + {savingState === SavingState.STARTED ? ( + - ) : value ? ( + + ) : value && !props.hideEditIcon ? ( + - ) : null} - + + ) : null} {isEditing && !!isInvalid ? ( diff --git a/app/client/src/components/ads/EditableTextWrapper.tsx b/app/client/src/components/ads/EditableTextWrapper.tsx index 41d9a93931..7c8fa701b0 100644 --- a/app/client/src/components/ads/EditableTextWrapper.tsx +++ b/app/client/src/components/ads/EditableTextWrapper.tsx @@ -15,13 +15,14 @@ const Container = styled.div<{ savingState: SavingState; isInvalid: boolean; }>` + position: relative; .editable-text-container { justify-content: center; } &&& .${Classes.EDITABLE_TEXT}, .icon-wrapper { - padding: 5px 10px; - height: 25px; + padding: 5px 0px; + height: 31px; background-color: ${(props) => (props.isInvalid && props.isEditing) || props.savingState === SavingState.ERROR @@ -29,15 +30,14 @@ const Container = styled.div<{ : "transparent"}; } - &&&& .${Classes.EDITABLE_TEXT} { + &&&& .${Classes.EDITABLE_TEXT}:hover { ${(props) => !props.isEditing ? ` - padding-left: 0px; - padding-right: 0px; border-bottom-style: solid; border-bottom-width: 1px; width: fit-content; + max-width: 194px; ` : null} } @@ -47,6 +47,8 @@ const Container = styled.div<{ !props.isEditing ? ` min-width: 0px !important; + height: auto !important; + line-height: ${props.theme.typography.h4.lineHeight}px !important; ` : null} } @@ -57,15 +59,15 @@ const Container = styled.div<{ font-size: ${(props) => props.theme.typography.h4.fontSize}px; line-height: ${(props) => props.theme.typography.h4.lineHeight}px; letter-spacing: ${(props) => props.theme.typography.h4.letterSpacing}px; - font-weight: ${(props) => props.theme.typography.h4.fontWeight}px; - } - - .error-message { - margin-top: 2px; + font-weight: ${(props) => props.theme.typography.h4.fontWeight}; + padding-right: 0px; } .icon-wrapper { padding-bottom: 0px; + position: absolute; + right: 0; + top: 0; } `; diff --git a/app/client/src/components/ads/TextInput.tsx b/app/client/src/components/ads/TextInput.tsx index 251f029cf6..466c928e36 100644 --- a/app/client/src/components/ads/TextInput.tsx +++ b/app/client/src/components/ads/TextInput.tsx @@ -91,6 +91,14 @@ const StyledInput = styled.input< background-color: ${(props) => props.inputStyle.bgColor}; color: ${(props) => props.inputStyle.color}; + &:-internal-autofill-selected, + &:-webkit-autofill, + &:-webkit-autofill:hover, + &:-webkit-autofill:focus { + -webkit-box-shadow: 0 0 0 30px ${(props) => props.inputStyle.bgColor} inset !important; + -webkit-text-fill-color: ${(props) => props.inputStyle.color} !important; + } + &::placeholder { color: ${(props) => props.theme.colors.textInput.placeholder}; } @@ -199,3 +207,5 @@ const TextInput = forwardRef( TextInput.displayName = "TextInput"; export default TextInput; + +export type InputType = "text" | "password" | "number" | "email" | "tel"; diff --git a/app/client/src/components/ads/Toast.tsx b/app/client/src/components/ads/Toast.tsx index bd8f3f9b72..1feec4cde9 100644 --- a/app/client/src/components/ads/Toast.tsx +++ b/app/client/src/components/ads/Toast.tsx @@ -168,7 +168,8 @@ export const Toaster = { />, { toastId: toastId, - pauseOnHover: true, + pauseOnHover: !config.dispatchableAction && !config.hideProgressBar, + pauseOnFocusLoss: !config.dispatchableAction && !config.hideProgressBar, autoClose: false, closeOnClick: false, hideProgressBar: config.hideProgressBar, diff --git a/app/client/src/components/ads/formFields/FieldError.tsx b/app/client/src/components/ads/formFields/FieldError.tsx new file mode 100644 index 0000000000..a5459d0a67 --- /dev/null +++ b/app/client/src/components/ads/formFields/FieldError.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import styled from "styled-components"; +import { IntentColors } from "constants/DefaultTheme"; +// Note: This component is only for the input fields which donot have the +// popover error tooltip. This is also only for Appsmith components +// Not to be used in widgets / canvas + +const StyledError = styled.span<{ show: boolean }>` + text-align: left; + color: ${IntentColors.danger}; + font-size: ${(props) => props.theme.fontSizes[3]}px; + opacity: ${(props) => (props.show ? 1 : 0)}; + display: block; + position: relative; + margin-top: ${(props) => props.theme.spaces[1]}px; +`; + +type FormFieldErrorProps = { + error?: string; + className?: string; +}; + +export const FormFieldError = (props: FormFieldErrorProps) => { + return ( + + {props.error || " "} + + ); +}; + +export default FormFieldError; diff --git a/app/client/src/components/ads/formFields/FormGroup.tsx b/app/client/src/components/ads/formFields/FormGroup.tsx new file mode 100644 index 0000000000..f27dca7791 --- /dev/null +++ b/app/client/src/components/ads/formFields/FormGroup.tsx @@ -0,0 +1,22 @@ +import styled from "styled-components"; +import { FormGroup, Classes } from "@blueprintjs/core"; +import { getTypographyByKey } from "constants/DefaultTheme"; +type FormGroupProps = { + fill?: boolean; +}; +const StyledFormGroup = styled(FormGroup)` + & { + width: ${(props) => (props.fill ? "100%" : "auto")}; + &.${Classes.FORM_GROUP} { + margin: 0 0 ${(props) => props.theme.spaces[5]}px; + } + &.${Classes.FORM_GROUP} .${Classes.FORM_HELPER_TEXT} { + font-size: ${(props) => props.theme.fontSizes[3]}px; + } + &.${Classes.FORM_GROUP} .${Classes.LABEL} { + ${(props) => getTypographyByKey(props, "h5")} + color: ${(props) => props.theme.colors.textInput.normal.text}; + } + } +`; +export default StyledFormGroup; diff --git a/app/client/src/components/ads/formFields/FormMessage.tsx b/app/client/src/components/ads/formFields/FormMessage.tsx new file mode 100644 index 0000000000..3dd062d8ab --- /dev/null +++ b/app/client/src/components/ads/formFields/FormMessage.tsx @@ -0,0 +1,85 @@ +import React from "react"; +import styled from "styled-components"; +import { Intent } from "constants/DefaultTheme"; +import { getTypographyByKey } from "constants/DefaultTheme"; +import { Link } from "react-router-dom"; + +export type MessageAction = { + url?: string; + onClick?: () => void; + text: string; + intent: Intent; +}; + +const StyledMessage = styled.div<{ intent: Intent }>` + & { + ${(props) => getTypographyByKey(props, "p1")} + width: 100%; + padding: ${(props) => props.theme.spaces[4]}px; + color: ${(props) => props.theme.colors.formMessage.text[props.intent]}; + background-color: ${(props) => + props.theme.colors.formMessage.background[props.intent]}; + } +`; + +export const ActionsContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; +`; + +const StyledAction = styled.div<{ intent: Intent }>` + margin-top: ${(props) => props.theme.spaces[5]}px; + ${(props) => getTypographyByKey(props, "h5")} + font-weight: 600; + & a { + text-decoration: none; + color: ${(props) => props.theme.colors.formMessage.text[props.intent]}; + } +`; + +export const ActionButton = (props: MessageAction) => { + if (props.url) { + const isExternal = props.url.indexOf("//") !== -1; + return ( + + {isExternal ? ( + + {props.text} + + ) : ( + {props.text} + )} + + ); + } else if (props.onClick) { + return ( + + {props.text} + + ); + } + return null; +}; + +export type FormMessageProps = { + intent: Intent; + message: string; + actions?: MessageAction[]; +}; + +export const FormMessage = (props: FormMessageProps) => { + const actions = + props.actions && + props.actions.map((action) => ( + + )); + return ( + + {props.message} + {actions && {actions}} + + ); +}; + +export default FormMessage; diff --git a/app/client/src/components/ads/formFields/TextField.tsx b/app/client/src/components/ads/formFields/TextField.tsx new file mode 100644 index 0000000000..cef1273bca --- /dev/null +++ b/app/client/src/components/ads/formFields/TextField.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { + Field, + WrappedFieldMetaProps, + WrappedFieldInputProps, +} from "redux-form"; +import InputComponent, { InputType } from "../TextInput"; +import { Intent } from "constants/DefaultTheme"; +import FormFieldError from "./FieldError"; + +const renderComponent = ( + componentProps: FormTextFieldProps & { + meta: Partial; + input: Partial; + }, +) => { + const showError = componentProps.meta.touched && !componentProps.meta.active; + + return ( + + + + + ); +}; + +type FormTextFieldProps = { + name: string; + placeholder: string; + type?: InputType; + label?: string; + intent?: Intent; + disabled?: boolean; + autoFocus?: boolean; +}; + +const FormTextField = (props: FormTextFieldProps) => { + return ( + + + + ); +}; + +export default FormTextField; diff --git a/app/client/src/components/designSystems/appsmith/AutoToolTipComponent.tsx b/app/client/src/components/designSystems/appsmith/AutoToolTipComponent.tsx index 0617567cb9..a742989e1b 100644 --- a/app/client/src/components/designSystems/appsmith/AutoToolTipComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/AutoToolTipComponent.tsx @@ -1,11 +1,18 @@ import React, { createRef, useEffect, useState } from "react"; import { Tooltip } from "@blueprintjs/core"; import { CellWrapper } from "components/designSystems/appsmith/TableStyledWrappers"; +import styled from "styled-components"; + +const TooltipContentWrapper = styled.div<{ width: number }>` + word-break: break-all; + max-width: ${(props) => props.width}px; +`; const AutoToolTipComponent = (props: { isHidden?: boolean; children: React.ReactNode; title: string; + tableWidth?: number; }) => { const ref = createRef(); const [useToolTip, updateToolTip] = useState(false); @@ -23,7 +30,11 @@ const AutoToolTipComponent = (props: { + {props.title} + + } position="top" > {props.children} diff --git a/app/client/src/components/designSystems/appsmith/CascadeFields.tsx b/app/client/src/components/designSystems/appsmith/CascadeFields.tsx index 3151475c9f..256499c486 100644 --- a/app/client/src/components/designSystems/appsmith/CascadeFields.tsx +++ b/app/client/src/components/designSystems/appsmith/CascadeFields.tsx @@ -28,6 +28,9 @@ const StyledRemoveIcon = styled( padding: 0; position: relative; cursor: pointer; + &.hide-icon { + display: none; + } `; const LabelWrapper = styled.div` @@ -246,7 +249,7 @@ const RenderOptions = (props: { (i) => i.value === props.value, ); if (selectedOptions && selectedOptions.length) { - selectValue(selectedOptions[0].value); + selectValue(selectedOptions[0].label); } else { selectValue(props.placeholder); } @@ -290,6 +293,7 @@ type CascadeFieldProps = { value: any; operator: Operator; index: number; + hasAnyFilters: boolean; applyFilter: (filter: ReactTableFilter, index: number) => void; removeFilter: (index: number) => void; }; @@ -447,7 +451,7 @@ const CascadeField = (props: CascadeFieldProps) => { }; const Fields = (props: CascadeFieldProps & { state: CascadeFieldState }) => { - const { index, removeFilter, applyFilter } = props; + const { index, removeFilter, applyFilter, hasAnyFilters } = props; const [state, dispatch] = React.useReducer(CaseCaseFieldReducer, props.state); const handleRemoveFilter = () => { dispatch({ type: CascadeFieldActionTypes.DELETE_FILTER }); @@ -515,7 +519,9 @@ const Fields = (props: CascadeFieldProps & { state: CascadeFieldState }) => { height={16} width={16} color={Colors.RIVER_BED} - className="t--table-filter-remove-btn" + className={`t--table-filter-remove-btn ${ + hasAnyFilters ? "" : "hide-icon" + }`} /> {index === 1 ? ( diff --git a/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx b/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx index 9de8406e6d..4fbe08c827 100644 --- a/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/ReactTableComponent.tsx @@ -64,6 +64,7 @@ interface ReactTableComponentProps { multiRowSelection?: boolean; hiddenColumns?: string[]; columnNameMap?: { [key: string]: string }; + triggerRowSelection: boolean; columnTypeMap?: { [key: string]: { type: string; @@ -319,6 +320,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => { pageNo={props.pageNo - 1} updatePageNo={props.updatePageNo} columnActions={props.columnActions} + triggerRowSelection={props.triggerRowSelection} nextPageClick={() => { props.nextPageClick(); }} diff --git a/app/client/src/components/designSystems/appsmith/ScrollIndicator.tsx b/app/client/src/components/designSystems/appsmith/ScrollIndicator.tsx new file mode 100644 index 0000000000..e2fc432579 --- /dev/null +++ b/app/client/src/components/designSystems/appsmith/ScrollIndicator.tsx @@ -0,0 +1,94 @@ +import React, { useEffect, useState, useRef } from "react"; +import styled from "styled-components"; +import _ from "lodash"; +import { useSpring, animated, interpolate } from "react-spring"; + +const ScrollTrack = styled.div<{ + isVisible: boolean; +}>` + position: absolute; + z-index: 100; + top: 0; + right: 2px; + width: 4px; + height: 100%; + box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); + overflow: hidden; + opacity: ${(props) => (props.isVisible ? 1 : 0)}; + transition: opacity 0.15s ease-in; +`; + +const ScrollThumb = styled(animated.div)` + width: 4px; + background-color: #ebeef0aa; + border-radius: 3px; + transform: translate3d(0, 0, 0); +`; + +interface Props { + containerRef: React.RefObject; +} +const ScrollIndicator = ({ containerRef }: Props) => { + const [{ thumbPosition }, setThumbPosition] = useSpring(() => ({ + thumbPosition: 0, + config: { + clamp: true, + friction: 10, + precision: 0.1, + tension: 800, + }, + })); + const [isScrollVisible, setIsScrollVisible] = useState(false); + const thumbRef = useRef(null); + + useEffect(() => { + const handleContainerScroll = (e: any): void => { + setIsScrollVisible(true); + const thumbHeight = + e.target.offsetHeight / (e.target.scrollHeight / e.target.offsetHeight); + const thumbPosition = (e.target.scrollTop / e.target.offsetHeight) * 100; + /* set scroll thumb height */ + if (thumbRef.current) { + thumbRef.current.style.height = thumbHeight + "px"; + } + setThumbPosition({ + thumbPosition, + }); + }; + + containerRef.current?.addEventListener("scroll", handleContainerScroll); + + return () => { + containerRef.current?.removeEventListener( + "scroll", + handleContainerScroll, + ); + }; + }, []); + + useEffect(() => { + if (isScrollVisible) { + hideScrollbar(); + } + }, [isScrollVisible]); + + const hideScrollbar = _.debounce(() => { + setIsScrollVisible(false); + }, 1500); + + return ( + + `translate3d(0px, ${top}%, 0)`, + ), + }} + /> + + ); +}; + +export default ScrollIndicator; diff --git a/app/client/src/components/designSystems/appsmith/Table.tsx b/app/client/src/components/designSystems/appsmith/Table.tsx index 46a7bd65cf..6553457b68 100644 --- a/app/client/src/components/designSystems/appsmith/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/Table.tsx @@ -53,6 +53,7 @@ interface TableProps { selectedRowIndices: number[]; disableDrag: () => void; enableDrag: () => void; + triggerRowSelection: boolean; searchTableData: (searchKey: any) => void; filters?: ReactTableFilter[]; applyFilter: (filters: ReactTableFilter[]) => void; @@ -120,6 +121,7 @@ export const Table = (props: TableProps) => { height={props.height} tableSizes={tableSizes} id={`table${props.widgetId}`} + triggerRowSelection={props.triggerRowSelection} backgroundColor={Colors.ATHENS_GRAY_DARKER} > { }; }, ); - const showAddFilter = - filters.length >= 1 && filters[0].column && filters[0].condition; + const hasAnyFilters = !!( + filters.length >= 1 && + filters[0].column && + filters[0].condition + ); return ( { className="t--table-filter-toggle-btn" selected={selected} icon={ - showAddFilter ? ( + hasAnyFilters ? ( {filters.length} ) : null } @@ -194,6 +197,7 @@ const TableFilters = (props: TableFilterProps) => { condition={filter.condition} value={filter.value} columns={columns} + hasAnyFilters={hasAnyFilters} applyFilter={(filter: ReactTableFilter, index: number) => { const updatedFilters = props.filters ? [...props.filters] @@ -215,7 +219,7 @@ const TableFilters = (props: TableFilterProps) => { /> ); })} - {showAddFilter ? ( + {hasAnyFilters ? (