diff --git a/README.md b/README.md index bacaccf60b..861820a3ce 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,14 @@

-[![GitHub release](https://img.shields.io/github/release/getappsmith/appsmith/all?logo=GitHub)](https://github.com/appsmithorg/appsmith/releases/latest) +[![GitHub release](https://img.shields.io/github/v/release/appsmithorg/appsmith.svg?logo=GitHub)](https://github.com/appsmithorg/appsmith/releases/latest) [![Website](https://img.shields.io/website?url=https%3A%2F%2Fappsmith.com&logo=Appsmith)](https://appsmith.com) [![Chat on Discord](https://img.shields.io/badge/chat-Discord-violet?logo=discord)](https://discord.gg/rBTTVJp) [![Docs](https://img.shields.io/badge/docs-v1.x-brightgreen.svg?style=flat)](https://docs.appsmith.com) -

+

- Built with ❤︎ & empathy + Built with empathy, not just ❤︎

@@ -37,7 +37,7 @@ Appsmith provides a better way of building internal tools by visualising them as ## Features -* **Build custom UI**: Drag & drop, resize and style widgets **without HTLM / CSS**. [Read more](https://docs.appsmith.com/core-concepts/building-the-ui) +* **Build custom UI**: Drag & drop, resize and style widgets **without HTML / CSS**. [Read more](https://docs.appsmith.com/core-concepts/building-the-ui) * **Query data**: Query & update your database directly from the UI. Supports **postgres, mongo, REST & GraphQL APIs**. [Read more](https://docs.appsmith.com/core-concepts/building-the-ui/displaying-api-data) * **JS Logic**: Write snippets of business logic using JS to transform data, manipuate UI or trigger workflows * **Data Workflows**: Simple configuration to create flows when users interact with the UI. [Read more](https://docs.appsmith.com/core-concepts/building-the-ui/calling-apis-from-widgets) diff --git a/app/client/cypress/fixtures/testdata.json b/app/client/cypress/fixtures/testdata.json index 0d31926877..ea75e9ee3d 100644 --- a/app/client/cypress/fixtures/testdata.json +++ b/app/client/cypress/fixtures/testdata.json @@ -37,5 +37,9 @@ "prevUrl": ".data.previous}}", "methodsWithParam": "users?page=2", "invalidHeader": "invalid", - "invalidValue": "invalid" -} + "invalidValue": "invalid", + "Put": "PUT", + "Get": "GET", + "next": "?page=2&pageSize=10", + "prev": "?page=1&pageSize=10" +} \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_All_Verb_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_All_Verb_spec.js index a5a9c85f6f..166a3d6701 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_All_Verb_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_All_Verb_spec.js @@ -30,9 +30,10 @@ describe("API Panel Test Functionality", function() { .click({ force: true }) .focus() .type(json, { force: true }); + cy.WaitAutoSave(); + cy.RunAPI(); + cy.validateRequest(testdata.baseUrl2, testdata.methodput, testdata.Put); }); - cy.WaitAutoSave(); - cy.RunAPI(); cy.ResponseStatusCheck("200 OK"); cy.log("Response code check successful"); cy.ResponseCheck("updatedAt"); @@ -55,9 +56,10 @@ describe("API Panel Test Functionality", function() { .click({ force: true }) .focus() .type(json, { force: true }); + cy.WaitAutoSave(); + cy.RunAPI(); + cy.validateRequest(testdata.baseUrl2, testdata.methodpost, testdata.Post); }); - cy.WaitAutoSave(); - cy.RunAPI(); cy.ResponseStatusCheck("201 CREATED"); cy.log("Response code check successful"); cy.ResponseCheck("createdAt"); @@ -80,9 +82,14 @@ describe("API Panel Test Functionality", function() { .click({ force: true }) .focus() .type(json, { force: true }); + cy.WaitAutoSave(); + cy.RunAPI(); + cy.validateRequest( + testdata.baseUrl2, + testdata.methodpatch, + testdata.Patch, + ); }); - cy.WaitAutoSave(); - cy.RunAPI(); cy.ResponseStatusCheck("200 OK"); cy.log("Response code check successful"); cy.ResponseCheck("updatedAt"); @@ -101,6 +108,11 @@ describe("API Panel Test Functionality", function() { ); cy.WaitAutoSave(); cy.RunAPI(); + cy.validateRequest( + testdata.baseUrl2, + testdata.methodpatch, + testdata.Delete, + ); cy.ResponseStatusCheck("204 NO_CONTENT"); cy.log("Response code check successful"); }); @@ -112,6 +124,7 @@ describe("API Panel Test Functionality", function() { cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods); cy.WaitAutoSave(); cy.RunAPI(); + cy.validateRequest(testdata.baseUrl, testdata.methods, testdata.Get); cy.ResponseStatusCheck(testdata.successStatusCode); cy.log("Response code check successful"); cy.ResponseCheck(testdata.responsetext); @@ -120,12 +133,22 @@ describe("API Panel Test Functionality", function() { cy.selectPaginationType(apiwidget.paginationWithUrl); cy.enterUrl(apiname, apiwidget.panigationNextUrl, testdata.nextUrl); cy.clickTest(apiwidget.TestNextUrl); + cy.validateRequest( + testdata.baseUrl, + testdata.methods.concat(testdata.next), + testdata.Get, + ); cy.ResponseStatusCheck(testdata.successStatusCode); cy.log("Response code check successful"); cy.ResponseCheck("Josh M Krantz"); cy.log("Response data check successful"); cy.enterUrl(apiname, apiwidget.panigationPrevUrl, testdata.prevUrl); cy.clickTest(apiwidget.TestPreUrl); + cy.validateRequest( + testdata.baseUrl, + testdata.methods.concat(testdata.prev), + testdata.Get, + ); cy.ResponseStatusCheck(testdata.successStatusCode); cy.log("Response code check successful"); cy.ResponseCheck(testdata.responsetext); @@ -138,6 +161,7 @@ describe("API Panel Test Functionality", function() { cy.enterDatasourceAndPath(testdata.baseUrl, testdata.queryAndValue); cy.WaitAutoSave(); cy.RunAPI(); + cy.validateRequest(testdata.baseUrl, testdata.queryAndValue, testdata.Get); cy.ResponseStatusCheck("200 OK"); cy.log("Response code check successful"); cy.ResponseCheck(testdata.responsetext3); @@ -157,6 +181,7 @@ describe("API Panel Test Functionality", function() { ); cy.WaitAutoSave(); cy.RunAPI(); + cy.validateRequest(testdata.baseUrl, testdata.methods, testdata.Get); cy.ResponseStatusCheck("5000"); cy.log("Response code check successful"); cy.ResponseCheck("Invalid value for Content-Type"); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Edit_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Edit_spec.js index 7629cee44e..32ed8cc158 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Edit_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ApiPaneTests/API_Edit_spec.js @@ -9,8 +9,8 @@ describe("API Panel Test Functionality", function() { cy.CreateAPI("FirstAPI"); cy.log("Creation of FirstAPI Action successful"); cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods); - cy.WaitAutoSave(); - cy.RunAPI(); + cy.SaveAndRunAPI(); + cy.validateRequest(testdata.baseUrl, testdata.methods, testdata.Get); cy.ResponseStatusCheck(testdata.successStatusCode); cy.get(apiwidget.createApiOnSideBar) .first() diff --git a/app/client/cypress/locators/apiWidgetslocator.json b/app/client/cypress/locators/apiWidgetslocator.json index 4d94ca1b2a..263257a37c 100644 --- a/app/client/cypress/locators/apiWidgetslocator.json +++ b/app/client/cypress/locators/apiWidgetslocator.json @@ -34,5 +34,12 @@ "TestNextUrl": ".t--apiFormPaginationNextTest", "TestPreUrl": ".t--apiFormPaginationPrevTest", "EditApiName": "img[alt='Edit pen']", - "ApiName": ".t--action-name-edit-field span" + "ApiName": ".t--action-name-edit-field span", + "Request": "//li[text()='Request']", + "RequestURL": "(//span[@class='bp3-tree-node-label']/span)[1]", + "RequestMethod": "(//span[@class='bp3-tree-node-label']/span)[2]", + "content-Type": "(//span[@class='bp3-tree-node-label']/span)[3]", + "requestBody": "(//div[contains(@class,'bp3-collapse-body')]//textarea)[1]", + "showrequest": "span:contains('Show Request')", + "Responsetab": "//li[text()='Response Body']" } diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 65cac3a16d..183ce923da 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -377,6 +377,17 @@ Cypress.Commands.add("SaveAndRunAPI", () => { cy.RunAPI(); }); +Cypress.Commands.add("validateRequest", (baseurl, path, verb) => { + cy.xpath(apiwidget.Request) + .should("be.visible") + .click({ force: true }); + cy.xpath(apiwidget.RequestURL).contains(baseurl.concat(path)); + cy.xpath(apiwidget.RequestMethod).contains(verb); + cy.xpath(apiwidget.Responsetab) + .should("be.visible") + .click({ force: true }); +}); + Cypress.Commands.add("SelectAction", action => { cy.get(ApiEditor.ApiVerb) .first() @@ -471,9 +482,12 @@ Cypress.Commands.add("selectPaginationType", option => { }); Cypress.Commands.add("clickTest", testbutton => { + cy.wait(2000); + cy.wait("@saveAction"); cy.get(testbutton) .first() .click({ force: true }); + cy.wait("@postExecute"); }); Cypress.Commands.add("enterUrl", (apiname, url, value) => { diff --git a/app/client/src/widgets/TableWidget.tsx b/app/client/src/widgets/TableWidget.tsx index 4dad1fd1b2..a40569f824 100644 --- a/app/client/src/widgets/TableWidget.tsx +++ b/app/client/src/widgets/TableWidget.tsx @@ -34,14 +34,14 @@ class TableWidget extends BaseWidget { label: VALIDATION_TYPES.TEXT, selectedRowIndex: VALIDATION_TYPES.NUMBER, searchText: VALIDATION_TYPES.TEXT, - // columnActions: VALIDATION_TYPES.ARRAY_ACTION_SELECTOR, - // onRowSelected: VALIDATION_TYPES.ACTION_SELECTOR, - // onPageChange: VALIDATION_TYPES.ACTION_SELECTOR, + filteredTableData: VALIDATION_TYPES.TABLE_DATA, }; } static getDerivedPropertiesMap() { return { - selectedRow: "{{this.tableData[this.selectedRowIndex]}}", + filteredTableData: + "{{!this.onSearchTextChanged ? this.tableData.filter((item) => Object.values(item).join(', ').toUpperCase().includes(this.searchText.toUpperCase())) : this.tableData}}", + selectedRow: "{{this.filteredTableData[this.selectedRowIndex]}}", }; } @@ -51,6 +51,8 @@ class TableWidget extends BaseWidget { pageSize: undefined, selectedRowIndex: -1, searchText: "", + // The following meta property is used for rendering the table. + filteredTableData: [], }; } @@ -198,26 +200,10 @@ class TableWidget extends BaseWidget { return updatedTableData; }; - searchTableData = (tableData: object[]) => { - if (!tableData || !tableData.length) { - return []; - } - const searchKey = - this.props.searchText !== undefined - ? this.props.searchText.toString().toUpperCase() - : ""; - return tableData.filter((item: object) => { - return Object.values(item) - .join(", ") - .toUpperCase() - .includes(searchKey); - }); - }; - getPageView() { - const { tableData, hiddenColumns } = this.props; + const { tableData, hiddenColumns, filteredTableData } = this.props; const tableColumns = this.getTableColumns(tableData); - const filteredTableData = this.searchTableData(tableData); + // Use the filtered data to render the table. const transformedData = this.transformData(filteredTableData, tableColumns); const serverSidePaginationEnabled = (this.props .serverSidePaginationEnabled && diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java index f4a95ee3c3..82492504ec 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceContextServiceImpl.java @@ -146,7 +146,7 @@ public class DatasourceContextServiceImpl implements DatasourceContextService { @Override public AuthenticationDTO decryptSensitiveFields(AuthenticationDTO authenticationDTO) { - if (authenticationDTO.getPassword() != null) { + if (authenticationDTO != null && authenticationDTO.getPassword() != null) { authenticationDTO.setPassword(encryptionService.decryptString(authenticationDTO.getPassword())); } return authenticationDTO; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java index ee62513f7b..52bccd314c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java @@ -18,6 +18,7 @@ import com.appsmith.server.repositories.OrganizationRepository; import com.appsmith.server.repositories.PageRepository; import com.appsmith.server.services.ActionService; import com.appsmith.server.services.ApplicationPageService; +import com.appsmith.server.services.DatasourceContextService; import com.appsmith.server.services.DatasourceService; import com.appsmith.server.services.OrganizationService; import com.appsmith.server.services.SessionUserService; @@ -51,6 +52,7 @@ public class ExamplesOrganizationCloner { private final SessionUserService sessionUserService; private final UserService userService; private final ApplicationPageService applicationPageService; + private final DatasourceContextService datasourceContextService; public Mono cloneExamplesOrganization() { return sessionUserService @@ -230,6 +232,9 @@ public class ExamplesOrganizationCloner { makePristine(datasource); datasource.setOrganizationId(toOrganizationId); datasource.setName(datasource.getName()); + if (datasource.getDatasourceConfiguration() != null) { + datasourceContextService.decryptSensitiveFields(datasource.getDatasourceConfiguration().getAuthentication()); + } return Mono.zip( Mono.just(templateDatasourceId), datasourceService.create(datasource) diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java index 89f9613ccb..808a8ffad4 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java @@ -1,5 +1,6 @@ package com.appsmith.server.solutions; +import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Property; import com.appsmith.server.constants.FieldName; @@ -17,6 +18,7 @@ import com.appsmith.server.services.ActionService; import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.ApplicationService; import com.appsmith.server.services.DatasourceService; +import com.appsmith.server.services.EncryptionService; import com.appsmith.server.services.OrganizationService; import com.appsmith.server.services.PageService; import com.appsmith.server.services.SessionUserService; @@ -88,6 +90,9 @@ public class ExamplesOrganizationClonerTests { @Autowired private PluginRepository pluginRepository; + @Autowired + private EncryptionService encryptionService; + @MockBean private PluginExecutorHelper pluginExecutorHelper; @@ -324,6 +329,9 @@ public class ExamplesOrganizationClonerTests { final Datasource ds2 = new Datasource(); ds2.setName("datasource 2"); ds2.setOrganizationId(organization.getId()); + ds2.setDatasourceConfiguration(new DatasourceConfiguration()); + ds2.getDatasourceConfiguration().setAuthentication(new AuthenticationDTO()); + ds2.getDatasourceConfiguration().getAuthentication().setPassword("answer-to-life"); return Mono.when( datasourceService.create(ds1), @@ -354,6 +362,14 @@ public class ExamplesOrganizationClonerTests { new Property("X-Answer", "42") ); + final Datasource ds2 = data.datasources.stream() + .filter(datasource -> "datasource 2".equals(datasource.getName())) + .findFirst() + .orElseThrow(); + assertThat(ds2.getDatasourceConfiguration().getAuthentication()).isNotNull(); + assertThat(ds2.getDatasourceConfiguration().getAuthentication().getPassword()) + .isEqualTo(encryptionService.encryptString("answer-to-life")); + assertThat(data.applications).isEmpty(); assertThat(data.actions).isEmpty(); })