diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Provider.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Provider.java index bd9d78bf5c..9c5b65a46d 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Provider.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Provider.java @@ -1,6 +1,5 @@ package com.appsmith.external.models; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -28,7 +27,6 @@ public class Provider extends BaseDomain { String documentationUrl; //URL which points to the homepage of the documentations here - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) String credentialSteps; //How to generate/get the credentials to run the APIs which belong to this provider List categories; //Category names here diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java index bcebc03ce7..afe0f4d691 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java @@ -13,6 +13,8 @@ import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; import com.mongodb.MongoClient; import com.mongodb.MongoClientURI; +import com.mongodb.MongoTimeoutException; +import com.mongodb.client.ClientSession; import com.mongodb.client.MongoDatabase; import lombok.extern.slf4j.Slf4j; import org.bson.Document; @@ -26,6 +28,8 @@ import org.springframework.util.StringUtils; import reactor.core.publisher.Mono; import java.math.BigInteger; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -153,7 +157,14 @@ public class MongoPlugin extends BasePlugin { public static MongoClientURI buildClientURI(DatasourceConfiguration datasourceConfiguration) { StringBuilder builder = new StringBuilder(); - boolean isSrv = Connection.Type.REPLICA_SET.equals(datasourceConfiguration.getConnection().getType()); + final Connection connection = datasourceConfiguration.getConnection(); + final List endpoints = datasourceConfiguration.getEndpoints(); + + // Use SRV mode if using REPLICA_SET, AND a port is not specified in the first endpoint. In SRV mode, the + // host and port details of individual shards will be obtained from the TXT records of the first endpoint. + boolean isSrv = Connection.Type.REPLICA_SET.equals(connection.getType()) + && endpoints.get(0).getPort() == null; + if (isSrv) { builder.append("mongodb+srv://"); } else { @@ -163,13 +174,13 @@ public class MongoPlugin extends BasePlugin { AuthenticationDTO authentication = datasourceConfiguration.getAuthentication(); if (authentication != null) { builder - .append(authentication.getUsername()) + .append(urlEncode(authentication.getUsername())) .append(':') - .append(authentication.getPassword()) + .append(urlEncode(authentication.getPassword())) .append('@'); } - for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) { + for (Endpoint endpoint : endpoints) { builder.append(endpoint.getHost()); if (endpoint.getPort() != null) { builder.append(':').append(endpoint.getPort()); @@ -183,11 +194,22 @@ public class MongoPlugin extends BasePlugin { // Delete the trailing comma. builder.deleteCharAt(builder.length() - 1); + boolean addedFinalSlash = false; if (authentication != null) { builder.append('/').append(authentication.getDatabaseName()); + addedFinalSlash = true; } - return new MongoClientURI(builder.toString()); + if (connection.getSsl() != null) { + if (!addedFinalSlash) { + builder.append('/'); + } + builder.append("?ssl=true"); + } + + final String uri = builder.toString(); + log.info("MongoPlugin URI: `{}`.", uri); + return new MongoClientURI(uri); } @Override @@ -207,13 +229,9 @@ public class MongoPlugin extends BasePlugin { invalids.add("Missing endpoint(s)."); } else if (Connection.Type.REPLICA_SET.equals(datasourceConfiguration.getConnection().getType())) { - if (endpoints.size() > 1) { - invalids.add("Direct connections cannot be used with multiple endpoints." + - " Please provide a single endpoint."); - } - - if (endpoints.get(0).getPort() != null) { - invalids.add("Port should not be set for REPLICA_SET connections."); + if (endpoints.size() == 1 && endpoints.get(0).getPort() != null) { + invalids.add("REPLICA_SET connections should not be given a port." + + " If you are trying to specify all the shards, please add more than one."); } } @@ -253,12 +271,28 @@ public class MongoPlugin extends BasePlugin { public Mono testDatasource(DatasourceConfiguration datasourceConfiguration) { return datasourceCreate(datasourceConfiguration) .map(mongoClient -> { + ClientSession clientSession = null; + try { - if (mongoClient != null) { + // Not using try-with-resources here since we want to close the *session* before closing the + // MongoClient instance. + clientSession = ((MongoClient) mongoClient).startSession(); + + } catch (MongoTimeoutException e) { + log.warn("Timeout connecting to MongoDB from MongoPlugin.", e); + return new DatasourceTestResult("Timed out trying to connect to MongoDB host."); + + } catch (Exception e) { + return new DatasourceTestResult(e.getMessage()); + + } finally { + if (clientSession != null) { + clientSession.close(); + } + if (mongoClient instanceof MongoClient) { ((MongoClient) mongoClient).close(); } - } catch (Exception e) { - log.warn("Error closing MongoDB connection that was made for testing.", e); + } return new DatasourceTestResult(); @@ -266,6 +300,10 @@ public class MongoPlugin extends BasePlugin { .onErrorResume(error -> Mono.just(new DatasourceTestResult(error.getMessage()))); } + private static String urlEncode(String text) { + return URLEncoder.encode(text, StandardCharsets.UTF_8); + } + } } diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/form.json new file mode 100644 index 0000000000..bebe615965 --- /dev/null +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/form.json @@ -0,0 +1,193 @@ +{ + "form": [ + { + "sectionName": "General", + "children": [ + { + "label": "Connection Mode", + "configProperty": "datasourceConfiguration.connection.mode", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Read Only", + "value": "READ_ONLY" + }, + { + "label": "Read / Write", + "value": "READ_WRITE" + } + ] + }, + { + "label": "Connection Type", + "configProperty": "datasourceConfiguration.connection.type", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Direct Connection", + "value": "DIRECT" + }, + { + "label": "Replica set", + "value": "REPLICA_SET" + } + ] + }, + { + "sectionName": null, + "children": [ + { + "label": "Host Address", + "configProperty": "datasourceConfiguration.endpoints[*].host", + "controlType": "KEYVALUE_ARRAY" + }, + { + "label": "Port", + "configProperty": "datasourceConfiguration.endpoints[*].port", + "dataType": "NUMBER", + "controlType": "KEYVALUE_ARRAY" + } + ] + }, + { + "label": "Default Database Name", + "placeholder": "(Optional)", + "configProperty": "datasourceConfiguration.authentication.databaseName", + "controlType": "INPUT_TEXT" + } + ] + }, + { + "sectionName": "Authentication", + "children": [ + { + "label": "Authentication Database Name", + "configProperty": "datasourceConfiguration.authentication.databaseName", + "controlType": "INPUT_TEXT" + }, + { + "label": "Authentication Type", + "configProperty": "datasourceConfiguration.authentication.authType", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "SCRAM-SHA-1", + "value": "SCRAM_SHA_1" + }, + { + "label": "SCRAM-SHA-256", + "value": "SCRAM_SHA_256" + }, + { + "label": "MONGODB-CR", + "value": "MONGODB_CR" + } + ] + }, + { + "sectionName": null, + "children": [ + { + "label": "Username", + "configProperty": "datasourceConfiguration.authentication.username", + "controlType": "INPUT_TEXT" + }, + { + "label": "Password", + "configProperty": "datasourceConfiguration.authentication.password", + "dataType": "PASSWORD", + "controlType": "INPUT_TEXT" + } + ] + } + ] + }, + { + "sectionName": "SSL (optional)", + "children": [ + { + "label": "Authentication Mechanism", + "configProperty": "datasourceConfiguration.connection.ssl.authType", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "CA Certificate", + "value": "CA_CERTIFICATE" + }, + { + "label": "Self Signed Certificate", + "value": "SELF_SIGNED_CERTIFICATE" + } + ] + }, + { + "label": "CA Certificate", + "configProperty": "datasourceConfiguration.connection.ssl.caCertificate", + "controlType": "FILE_PICKER" + }, + { + "label": "PEM Certificate", + "configProperty": "datasourceConfiguration.connection.ssl.pemCertificate", + "controlType": "FILE_PICKER" + } + ] + }, + { + "sectionName": "SSH Tunnel (optional)", + "children": [ + { + "label": "Enable SSH Tunneling", + "configProperty": "sshTunneling", + "controlType": "SWITCH" + }, + { + "sectionName": null, + "children": [ + { + "label": "SSH Address", + "configProperty": "datasourceConfiguration.sshProxy.host", + "controlType": "INPUT_TEXT" + }, + { + "label": "Port", + "configProperty": "datasourceConfiguration.sshProxy.port", + "controlType": "INPUT_TEXT" + } + ] + }, + { + "label": "Username", + "configProperty": "datasourceConfiguration.sshProxy.username", + "controlType": "INPUT_TEXT" + }, + { + "label": "Authentication Type", + "configProperty": "datasourceConfiguration.authenticationType", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Password", + "value": "PASSWORD" + }, + { + "label": "Identity File", + "value": "IDENTITY_FILE" + } + ] + }, + { + "label": "Password", + "configProperty": "datasourceConfiguration.ssh.tunnelPassword", + "dataType": "PASSWORD", + "controlType": "INPUT_TEXT" + }, + { + "label": "Passphrase", + "configProperty": "datasourceConfiguration.ssh.tunnelPassphrase", + "dataType": "PASSWORD", + "controlType": "INPUT_TEXT" + } + ] + } + ] +} diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/postgresPlugin/src/main/resources/form.json new file mode 100644 index 0000000000..122a5b367c --- /dev/null +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/resources/form.json @@ -0,0 +1,194 @@ +{ + "form": [ + { + "sectionName": "General", + "id": 1, + "children": [ + { + "label": "Connection Mode", + "configProperty": "datasourceConfiguration.connection.mode", + "controlType": "DROP_DOWN", + "isRequired": true, + "options": [ + { + "label": "Read Only", + "value": "READ_ONLY" + }, + { + "label": "Read / Write", + "value": "READ_WRITE" + } + ] + } + ] + }, + { + "sectionName": "Connection", + "id": 2, + "children": [ + { + "sectionName": null, + "children": [ + { + "label": "Host Address", + "configProperty": "datasourceConfiguration.endpoints[*].host", + "controlType": "KEYVALUE_ARRAY" + }, + { + "label": "Port", + "configProperty": "datasourceConfiguration.endpoints[*].port", + "dataType": "NUMBER", + "controlType": "KEYVALUE_ARRAY" + } + ] + }, + { + "label": "Database Name", + "configProperty": "datasourceConfiguration.authentication.databaseName", + "controlType": "INPUT_TEXT" + }, + { + "sectionName": null, + "children": [ + { + "label": "Username", + "configProperty": "datasourceConfiguration.authentication.username", + "controlType": "INPUT_TEXT" + }, + { + "label": "Password", + "configProperty": "datasourceConfiguration.authentication.password", + "dataType": "PASSWORD", + "controlType": "INPUT_TEXT" + } + ] + } + ] + }, + { + "id": 3, + "sectionName": "SSL (optional)", + "children": [ + { + "label": "SSL Mode", + "configProperty": "datasourceConfiguration.connection.ssl.authType", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Allow", + "value": "ALLOW" + }, + { + "label": "Prefer", + "value": "PREFER" + }, + { + "label": "Require", + "value": "REQUIRE" + }, + { + "label": "Disable", + "value": "DISABLE" + }, + { + "label": "Verify-CA", + "value": "VERIFY_CA" + }, + { + "label": "Verify-Full", + "value": "VERIFY_FULL" + } + ] + }, + { + "sectionName": null, + "children": [ + { + "label": "Key File", + "configProperty": "datasourceConfiguration.connection.ssl.keyFile", + "controlType": "FILE_PICKER" + }, + { + "label": "Certificate", + "configProperty": "datasourceConfiguration.connection.ssl.certificateFile", + "controlType": "FILE_PICKER" + } + ] + }, + { + "sectionName": null, + "children": [ + { + "label": "CA Certificate", + "configProperty": "datasourceConfiguration.connection.ssl.caCertificateFile", + "controlType": "FILE_PICKER" + }, + { + "label": "PEM Certificate", + "configProperty": "datasourceConfiguration.connection.ssl.pemCertificate.file", + "controlType": "FILE_PICKER" + }, + { + "label": "PEM Passphrase", + "configProperty": "datasourceConfiguration.connection.ssl.pemCertificate.password", + "dataType": "PASSWORD", + "controlType": "INPUT_TEXT" + } + ] + } + ] + }, + { + "sectionName": "SSH (optional)", + "id": 4, + "children": [ + { + "label": "Enable SSH", + "configProperty": "enableSSH", + "controlType": "SWITCH" + }, + { + "sectionName": null, + "children": [ + { + "label": "Tunnel Host", + "configProperty": "datasourceConfiguration.sshProxy.host", + "controlType": "INPUT_TEXT" + }, + { + "label": "Tunnel Port", + "configProperty": "datasourceConfiguration.sshProxy.port", + "controlType": "INPUT_TEXT" + } + ] + }, + { + "label": "Username", + "configProperty": "datasourceConfiguration.sshProxy.username", + "controlType": "INPUT_TEXT" + }, + { + "label": "Password", + "configProperty": "datasourceConfiguration.sshProxy.password", + "dataType": "PASSWORD", + "controlType": "INPUT_TEXT" + }, + { + "label": "Authentication Type", + "configProperty": "datasourceConfiguration.sshProxy.authType", + "controlType": "DROP_DOWN", + "options": [ + { + "label": "Password", + "value": "PASSWORD" + }, + { + "label": "Identity File", + "value": "IDENTITY_FILE" + } + ] + } + ] + } + ] +} diff --git a/app/server/appsmith-plugins/rapidApiPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/rapidApiPlugin/src/main/resources/form.json new file mode 100644 index 0000000000..4602da5aec --- /dev/null +++ b/app/server/appsmith-plugins/rapidApiPlugin/src/main/resources/form.json @@ -0,0 +1,15 @@ +{ + "form": [ + { + "sectionName": "General", + "id": 1, + "children": [ + { + "label": "Rapid Api Connection Name", + "configProperty": "connectionName", + "controlType": "INPUT_TEXT" + } + ] + } + ] +} diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java index a40039aaac..ae1413a172 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/plugins/RestApiPlugin.java @@ -241,8 +241,13 @@ public class RestApiPlugin extends BasePlugin { return Mono.just(new DatasourceTestResult("Invalid URL: '" + e.getMessage() + "'.")); } + int port = url.getPort(); + if (port <= 0) { + return Mono.just(new DatasourceTestResult("Invalid Port: '" + port + "'.")); + } + try (Socket socket = new Socket()) { - socket.connect(new InetSocketAddress(url.getHost(), url.getPort()), 300); + socket.connect(new InetSocketAddress(url.getHost(), port), 300); } catch (IOException e) { return Mono.just( @@ -262,7 +267,8 @@ public class RestApiPlugin extends BasePlugin { if (StringUtils.isNotEmpty(key)) { String value = header.getValue(); webClientBuilder.defaultHeader(key, value); - if (key.equals("Content-Type") && value.equals(MediaType.APPLICATION_JSON_VALUE)) { + + if (key.toLowerCase().equals(HttpHeaders.CONTENT_TYPE.toLowerCase()) && value.equals(MediaType.APPLICATION_JSON_VALUE)) { isContentTypeJson = true; } } diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/restApiPlugin/src/main/resources/form.json new file mode 100644 index 0000000000..4602da5aec --- /dev/null +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/resources/form.json @@ -0,0 +1,15 @@ +{ + "form": [ + { + "sectionName": "General", + "id": 1, + "children": [ + { + "label": "Rapid Api Connection Name", + "configProperty": "connectionName", + "controlType": "INPUT_TEXT" + } + ] + } + ] +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/DatasourceController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/DatasourceController.java index 586b212733..052dbcd59c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/DatasourceController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/DatasourceController.java @@ -6,6 +6,7 @@ import com.appsmith.server.domains.Datasource; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.services.DatasourceService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -41,10 +42,6 @@ public class DatasourceController extends BaseController { - ResponseDTO response = new ResponseDTO<>(); - response.setData(testResult); - return response; - }); + .map(testResult -> new ResponseDTO<>(HttpStatus.OK.value(), testResult, null)); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/MarketplaceController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/MarketplaceController.java index ba2dd13d62..f73dd420e6 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/MarketplaceController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/MarketplaceController.java @@ -1,25 +1,13 @@ package com.appsmith.server.controllers; -import com.appsmith.external.models.ActionConfiguration; -import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.ApiTemplate; -import com.appsmith.external.models.ApiTemplateConfiguration; -import com.appsmith.external.models.DatasourceConfiguration; -import com.appsmith.external.models.Property; -import com.appsmith.external.models.Provider; -import com.appsmith.external.models.Statistics; import com.appsmith.server.constants.Url; -import com.appsmith.server.domains.Action; -import com.appsmith.server.domains.Datasource; import com.appsmith.server.dtos.ProviderPaginatedDTO; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.dtos.SearchResponseDTO; import com.appsmith.server.services.MarketplaceService; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; @@ -28,7 +16,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; -import java.util.ArrayList; import java.util.List; @RestController @@ -46,78 +33,13 @@ public class MarketplaceController { @GetMapping("/search") Mono> searchAPIOrProviders(@RequestParam String searchKey, @RequestParam(required = false) Integer limit) { - SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); - List providers = new ArrayList<>(); - Provider provider = new Provider(); - List categories = new ArrayList<>(); - categories.add("Data"); - categories.add("Sports"); - provider.setCategories(categories); - provider.setName("New Sports Ltd"); - provider.setId("RandomSavedId"); - provider.setDescription("Some description here"); - provider.setUrl("http://url.com"); - provider.setImageUrl("http://image.url.com"); - provider.setDocumentationUrl("http://docu.url.com"); - Statistics statistics = new Statistics(); - statistics.setAverageLatency((long) 230); - statistics.setImports((long) 1000); - statistics.setSuccessRate(99.7); - provider.setStatistics(statistics); - provider.setCredentialSteps("Credential steps here"); - - providers.add(provider); - searchResponseDTO.setProviders(providers); - - List apiTemplates = new ArrayList<>(); - ApiTemplate apiTemplate = new ApiTemplate(); - apiTemplate.setId("Id"); - apiTemplate.setPackageName("restapi-plugin"); - DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); - datasourceConfiguration.setUrl("http://google.com"); - apiTemplate.setDatasourceConfiguration(datasourceConfiguration); - ActionConfiguration actionConfiguration = new ActionConfiguration(); - actionConfiguration.setPath("/viewSomething"); - actionConfiguration.setHttpMethod(HttpMethod.GET); - List headers = new ArrayList<>(); - Property header = new Property(); - header.setKey("key"); - header.setValue("value"); - headers.add(header); - actionConfiguration.setHeaders(headers); - apiTemplate.setActionConfiguration(actionConfiguration); - - ApiTemplateConfiguration apiTemplateConfiguration = new ApiTemplateConfiguration(); - apiTemplateConfiguration.setDocumentation("documentation"); - apiTemplateConfiguration.setDocumentationUrl("http://url.com"); - ActionExecutionResult actionExecutionResult = new ActionExecutionResult(); - actionExecutionResult.setBody("body"); - actionExecutionResult.setStatusCode("200"); - String headersInString = "{\\r\\n \\\"Date\\\": [\\r\\n \\\"Thu, 06 Feb 2020 13:09:27 GMT\\\"\\r\\n ],\\r\\n \\\"Content-Type\\\": [\\r\\n \\\"application\\/json; charset=utf-8\\\"\\r\\n ],\\r\\n \\\"Connection\\\": [\\r\\n \\\"keep-alive\\\"\\r\\n ],\\r\\n \\\"Set-Cookie\\\": [\\r\\n \\\"__cfduid=dcdfc69e4b24eed1dbde13490b60fa2931580994567; expires=Sat, 07-Mar-20 13:09:27 GMT; path=\\/; domain=.pokeapi.co; HttpOnly; SameSite=Lax; Secure\\\"\\r\\n ],\\r\\n \\\"access-control-allow-origin\\\": [\\r\\n \\\"*\\\"\\r\\n ],\\r\\n \\\"cache-control\\\": [\\r\\n \\\"public, max-age=86400, s-maxage=86400\\\"\\r\\n ],\\r\\n \\\"etag\\\": [\\r\\n \\\"W\\/\\\\\\\"5bc-hMqibo\\/v586SwY5Pw+N9QHVbp1M\\\\\\\"\\\"\\r\\n ],\\r\\n \\\"function-execution-id\\\": [\\r\\n \\\"aclq7ln3lowb\\\"\\r\\n ],\\r\\n \\\"x-powered-by\\\": [\\r\\n \\\"Express\\\"\\r\\n ],\\r\\n \\\"x-cloud-trace-context\\\": [\\r\\n \\\"f838c06278fff2cc715ed3f4cc8c27f6\\\"\\r\\n ],\\r\\n \\\"X-Served-By\\\": [\\r\\n \\\"cache-sin18040-SIN\\\"\\r\\n ],\\r\\n \\\"X-Cache\\\": [\\r\\n \\\"HIT\\\"\\r\\n ],\\r\\n \\\"X-Cache-Hits\\\": [\\r\\n \\\"1\\\"\\r\\n ],\\r\\n \\\"X-Timer\\\": [\\r\\n \\\"S1564191065.116552,VS0,VE1\\\"\\r\\n ],\\r\\n \\\"Vary\\\": [\\r\\n \\\"accept-encoding, x-fh-requested-host, cookie, authorization\\\"\\r\\n ],\\r\\n \\\"CF-Cache-Status\\\": [\\r\\n \\\"REVALIDATED\\\"\\r\\n ],\\r\\n \\\"Accept-Ranges\\\": [\\r\\n \\\"bytes\\\"\\r\\n ],\\r\\n \\\"Expect-CT\\\": [\\r\\n \\\"max-age=604800, report-uri=\\\\\\\"https:\\/\\/report-uri.cloudflare.com\\/cdn-cgi\\/beacon\\/expect-ct\\\\\\\"\\\"\\r\\n ],\\r\\n \\\"Server\\\": [\\r\\n \\\"cloudflare\\\"\\r\\n ],\\r\\n \\\"CF-RAY\\\": [\\r\\n \\\"560d5bce6a17d9e0-SIN\\\"\\r\\n ],\\r\\n \\\"transfer-encoding\\\": [\\r\\n \\\"chunked\\\"\\r\\n ]\\r\\n }"; - try { - JsonNode headersNode = objectMapper.readTree(headersInString); - actionExecutionResult.setHeaders(headersNode); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } - apiTemplateConfiguration.setSampleResponse(actionExecutionResult); - apiTemplate.setApiTemplateConfiguration(apiTemplateConfiguration); - apiTemplates.add(apiTemplate); - searchResponseDTO.setApiTemplates(apiTemplates); - - List actions = new ArrayList<>(); - Action action = new Action(); - action.setName("ResultActionAPI"); - Datasource datasource = new Datasource(); - datasource.setDatasourceConfiguration(datasourceConfiguration); - action.setActionConfiguration(actionConfiguration); - action.setDatasource(datasource); - actions.add(action); - searchResponseDTO.setActions(actions); - - return Mono.just(searchResponseDTO) - .map(result -> new ResponseDTO<>(HttpStatus.OK.value(), result, null)); + return marketplaceService.searchProviderByName(searchKey) + .map(result -> { + SearchResponseDTO searchResponseDTO = new SearchResponseDTO(); + searchResponseDTO.setProviders(result); + return new ResponseDTO<>(HttpStatus.OK.value(), searchResponseDTO, null); + }); } @GetMapping("/templates") diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PluginController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PluginController.java index 828b3c30af..79513f2000 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PluginController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/PluginController.java @@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -51,4 +52,10 @@ public class PluginController extends BaseController new ResponseDTO<>(HttpStatus.OK.value(), resources, null)); } + + @GetMapping("/{pluginId}/form") + public Mono> getDatasourceForm(@PathVariable String pluginId) { + return service.getFormConfig(pluginId) + .map(form -> new ResponseDTO<>(HttpStatus.OK.value(), form, null)); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java index 0992286be6..fd089e2a3e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java @@ -40,7 +40,8 @@ public enum AppsmithError { PLUGIN_INSTALLATION_FAILED_DOWNLOAD_ERROR(500, 5002, "Plugin installation failed due to an error while downloading it. Check the jar location & try again."), PLUGIN_RUN_FAILED(500, 5003, "Plugin execution failed with error {0}"), PLUGIN_EXECUTION_TIMEOUT(504, 5040, "Plugin Execution exceeded the maximum allowed time. Please increase the timeout in your action settings or check your backend action endpoint"), - MARKETPLACE_TIMEOUT(504, 5041, "Marketplace is responding too slowly. Please check the internet connection"); + PLUGIN_LOAD_FORM_JSON_FAIL(500, 5004, "Unable to load datasource form configuration. Details: {0}."), + MARKETPLACE_TIMEOUT(504, 5041, "Marketplace is responding too slowly. Please try again later"); private Integer httpErrorCode; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java index 8adef5e15a..f87bce2b47 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java @@ -20,6 +20,7 @@ import net.minidev.json.parser.JSONParser; import net.minidev.json.parser.ParseException; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import reactor.core.publisher.Flux; @@ -434,8 +435,11 @@ public class LayoutActionServiceImpl implements LayoutActionService { for (int i = 0; i < children.size(); i++) { Map data = (Map) children.get(i); JSONObject object = new JSONObject(); - object.putAll(data); - extractAllWidgetNamesFromDSL(object, widgetNames); + // If the children tag exists and there are entries within it + if (!CollectionUtils.isEmpty(data)) { + object.putAll(data); + extractAllWidgetNamesFromDSL(object, widgetNames); + } } } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/MarketplaceService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/MarketplaceService.java index 723b16416c..16fd698303 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/MarketplaceService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/MarketplaceService.java @@ -9,6 +9,7 @@ import reactor.core.publisher.Mono; import java.util.List; public interface MarketplaceService { + Mono getProviders(MultiValueMap params); Mono> getTemplates(MultiValueMap params); @@ -16,6 +17,8 @@ public interface MarketplaceService { Mono> getCategories(); Mono subscribeAndUpdateStatisticsOfProvider(String providerId); - + Mono getProviderById(String id); + + Mono> searchProviderByName(String id); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/MarketplaceServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/MarketplaceServiceImpl.java index 9f76334957..37d734707b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/MarketplaceServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/MarketplaceServiceImpl.java @@ -164,6 +164,33 @@ public class MarketplaceServiceImpl implements MarketplaceService { .doOnError(error -> Mono.error(new AppsmithException(AppsmithError.MARKETPLACE_TIMEOUT))); } + @Override + /** + * This function searches for providers and returns the providers with exact match in name. + * In the future the search should support 'like' for providers and search could expand to include + * the actions used in the organization (across all applications) and templates as well. + */ + public Mono> searchProviderByName(String name) { + URI uri = buildFullURI(null, PROVIDER_PATH + "/name/" + name); + + return webClient + .get() + .uri(uri) + .retrieve() + .bodyToMono(String.class) + .flatMap(stringBody -> { + List providers = null; + try { + providers = objectMapper.readValue(stringBody, ArrayList.class); + } catch (JsonProcessingException e) { + return Mono.error(new AppsmithException(AppsmithError.JSON_PROCESSING_ERROR, e)); + } + return Mono.just(providers); + }) + .timeout(Duration.ofMillis(timeoutInMillis)) + .doOnError(error -> Mono.error(new AppsmithException(AppsmithError.MARKETPLACE_TIMEOUT))); + } + private URI buildFullURI(MultiValueMap params, String path) { UriComponentsBuilder uriBuilder = UriComponentsBuilder.newInstance(); try { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginService.java index 42bbf8f0e3..630fca324f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginService.java @@ -7,6 +7,8 @@ import com.appsmith.server.dtos.PluginOrgDTO; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.io.InputStream; + public interface PluginService extends CrudService { Flux getDefaultPlugins(); @@ -22,4 +24,8 @@ public interface PluginService extends CrudService { Mono findById(String id); Plugin redisInstallPlugin(InstallPluginRedisDTO installPluginRedisDTO); + + Mono getFormConfig(String pluginId); + + Mono getPluginResource(String pluginId, String resourcePath); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java index 7c5e2a8c04..faca5ba8bb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java @@ -33,10 +33,13 @@ import reactor.core.scheduler.Scheduler; import javax.validation.Validator; import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Slf4j @@ -278,4 +281,33 @@ public class PluginServiceImpl extends BaseService getFormConfig(String pluginId) { + return getPluginResource(pluginId, "form.json") + .flatMap(jsonStream -> { + try { + return Mono.just(new ObjectMapper().readValue(jsonStream, Map.class)); + } catch (IOException e) { + return Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL, e.getMessage())); + } + }); + } + + @Override + public Mono getPluginResource(String pluginId, String resourcePath) { + return findById(pluginId) + .flatMap(plugin -> { + InputStream formResourceStream = pluginManager + .getPlugin(plugin.getPackageName()) + .getPluginClassLoader() + .getResourceAsStream(resourcePath); + + if (formResourceStream == null) { + return Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL, "Resource not found")); + } + + return Mono.just(formResourceStream); + }); + } }