From 99e3cb5d65938accbd18d6a2f1a656d722a2ac09 Mon Sep 17 00:00:00 2001 From: vivek-appsmith Date: Wed, 16 Apr 2025 17:27:17 +0530 Subject: [PATCH] feat(server): Enable upcoming integrations endpoint (#40256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds a new endpoint to the Plugin API that fetches upcoming integrations from Appsmith's cloud services. This feature enables users to discover and prepare for new integration options that will be available in future releases. ## Changes - Added a new getUpcomingIntegrations endpoint to the PluginController at GET /api/v1/plugins/upcoming-integrations - Implemented the corresponding service method in PluginService to fetch data from cloud services - Added proper error handling for cloud service communication issues - Injected CloudServicesConfig to ensure proper cloud service URLs are used - Added instanceId to all requests to cloud services for tracking and authorization ## Technical Details - Uses WebClientUtils for HTTP requests to external services - Uses ConfigService to retrieve the instance ID for authentication - Handles error responses gracefully, returning empty results instead of errors to clients - Returns properly formatted integration data including names, descriptions, and other metadata This enhancement will help users discover upcoming integration options and provide feedback on planned datasource connectors. /ok-to-test tags="@tag.Sanity" Solves https://github.com/appsmithorg/appsmith/issues/40048 > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: a9ae87419b119adb08a156bf19dbe9fe7dd16b64 > Cypress dashboard. > Tags: `@tag.Sanity` > Spec: >
Wed, 16 Apr 2025 11:50:36 UTC ## Summary by CodeRabbit - **New Features** - Added a new endpoint to fetch upcoming integrations, providing users with information about future external service integrations. - **Bug Fixes** - Improved error handling for external integration data fetches, ensuring a graceful fallback and user-friendly messaging if data cannot be retrieved. --------- Co-authored-by: nilansh --- .../server/controllers/PluginController.java | 8 ++- .../controllers/ce/PluginControllerCE.java | 22 +++++++ .../server/plugins/base/PluginServiceCE.java | 2 + .../plugins/base/PluginServiceCEImpl.java | 62 ++++++++++++++++++- .../plugins/base/PluginServiceImpl.java | 10 ++- .../services/ce/PluginServiceCEImplTest.java | 8 ++- 6 files changed, 106 insertions(+), 6 deletions(-) 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 695e757a3c..3b9aab6f4c 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 @@ -1,5 +1,6 @@ package com.appsmith.server.controllers; +import com.appsmith.server.configurations.CloudServicesConfig; import com.appsmith.server.constants.Url; import com.appsmith.server.controllers.ce.PluginControllerCE; import com.appsmith.server.plugins.base.PluginService; @@ -11,7 +12,10 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping(Url.PLUGIN_URL) public class PluginController extends PluginControllerCE { - public PluginController(PluginService service, PluginTriggerSolution pluginTriggerSolution) { - super(service, pluginTriggerSolution); + public PluginController( + PluginService service, + PluginTriggerSolution pluginTriggerSolution, + CloudServicesConfig cloudServicesConfig) { + super(service, pluginTriggerSolution, cloudServicesConfig); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PluginControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PluginControllerCE.java index 3811860740..e08f08ffb9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PluginControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PluginControllerCE.java @@ -3,10 +3,12 @@ package com.appsmith.server.controllers.ce; import com.appsmith.external.models.TriggerRequestDTO; import com.appsmith.external.models.TriggerResultDTO; import com.appsmith.external.views.Views; +import com.appsmith.server.configurations.CloudServicesConfig; import com.appsmith.server.constants.FieldName; import com.appsmith.server.constants.Url; import com.appsmith.server.domains.Plugin; import com.appsmith.server.dtos.ResponseDTO; +import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.plugins.base.PluginService; import com.appsmith.server.plugins.solutions.PluginTriggerSolution; import com.fasterxml.jackson.annotation.JsonView; @@ -28,6 +30,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; +import java.util.Map; @RequestMapping(Url.PLUGIN_URL) @RequiredArgsConstructor @@ -38,6 +41,8 @@ public class PluginControllerCE { private final PluginTriggerSolution pluginTriggerSolution; + private final CloudServicesConfig cloudServicesConfig; + @JsonView(Views.Public.class) @GetMapping public Mono>> getAll(@RequestParam String workspaceId) { @@ -96,4 +101,21 @@ public class PluginControllerCE { serverWebExchange.getRequest().getHeaders()) .map(triggerResultDTO -> new ResponseDTO<>(HttpStatus.OK, triggerResultDTO)); } + + @JsonView(Views.Public.class) + @GetMapping("/upcoming-integrations") + public Mono>>> getUpcomingIntegrations() { + log.debug("Fetching upcoming integrations from external API"); + return service.getUpcomingIntegrations() + .map(integrations -> new ResponseDTO<>(HttpStatus.OK, integrations)) + .onErrorResume(error -> { + if (error instanceof AppsmithException) { + log.warn("Cloud service error: {}", error.getMessage()); + return Mono.just(new ResponseDTO<>(HttpStatus.OK.value(), List.of(), error.getMessage())); + } + log.warn("Error retrieving upcoming integrations from external service: {}", error.getMessage()); + return Mono.just(new ResponseDTO<>( + HttpStatus.OK.value(), List.of(), "Unable to fetch upcoming integrations at this time")); + }); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCE.java index d2e588247f..6a62cbed9d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCE.java @@ -53,4 +53,6 @@ public interface PluginServiceCE extends CrudService { Mono> findAllPluginsInWorkspace(String workspaceId); Flux getPluginsByType(PluginType pluginType); + + Mono>> getUpcomingIntegrations(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCEImpl.java index bdbfe12114..4af6b9d78a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceCEImpl.java @@ -2,6 +2,7 @@ package com.appsmith.server.plugins.base; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.PluginType; +import com.appsmith.server.configurations.CloudServicesConfig; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.Workspace; @@ -15,7 +16,9 @@ import com.appsmith.server.helpers.LoadShifter; import com.appsmith.server.repositories.PluginRepository; import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.BaseService; +import com.appsmith.server.services.ConfigService; import com.appsmith.server.services.WorkspaceService; +import com.appsmith.util.WebClientUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; @@ -46,6 +49,7 @@ import java.io.InputStream; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -65,6 +69,7 @@ public class PluginServiceCEImpl extends BaseService reactiveTemplate; private final ChannelTopic topic; private final ObjectMapper objectMapper; + private final CloudServicesConfig cloudServicesConfig; private final Map>> formCache = new HashMap<>(); private final Map>> templateCache = new HashMap<>(); @@ -86,6 +91,8 @@ public class PluginServiceCEImpl extends BaseService reactiveTemplate, ChannelTopic topic, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + CloudServicesConfig cloudServicesConfig, + ConfigService configService) { super(validator, repository, analyticsService); this.workspaceService = workspaceService; this.pluginManager = pluginManager; this.reactiveTemplate = reactiveTemplate; this.topic = topic; this.objectMapper = objectMapper; + this.cloudServicesConfig = cloudServicesConfig; + this.configService = configService; } @Override @@ -660,6 +671,55 @@ public class PluginServiceCEImpl extends BaseService>> getUpcomingIntegrations() { + log.debug("Fetching upcoming integrations from external API"); + + return configService.getInstanceId().flatMap(instanceId -> { + String apiUrl = cloudServicesConfig.getBaseUrl() + + "/api/v1/config/external-saas/upcoming-integrations?instanceId=" + instanceId; + return WebClientUtils.create() + .get() + .uri(apiUrl) + .retrieve() + .bodyToMono(Map.class) + .flatMap(response -> { + // Extract the integrations list from the response + if (response.containsKey("data")) { + List> integrations = new ArrayList<>(); + List data = (List) response.get("data"); + for (Object item : data) { + if (item instanceof Map) { + integrations.add((Map) item); + } + } + return Mono.just(integrations); + } else if (response.containsKey("responseMeta")) { + Map responseMeta = (Map) response.get("responseMeta"); + if (responseMeta.containsKey("error")) { + Map error = (Map) responseMeta.get("error"); + if (error.containsKey("message")) { + String errorMessage = (String) error.get("message"); + return Mono.error(new AppsmithException( + AppsmithError.INSTANCE_REGISTRATION_FAILURE, errorMessage)); + } + } + return Mono.error(new RuntimeException("Unknown error in response metadata")); + } + return Mono.just(List.>of()); + }) + .onErrorResume(error -> { + if (error instanceof AppsmithException) { + return Mono.error(error); + } + log.warn( + "Error retrieving upcoming integrations from external service: {}", error.getMessage()); + return Mono.error( + new RuntimeException("Error retrieving upcoming integrations: " + error.getMessage())); + }); + }); + } + @Data static class PluginTemplatesMeta { List templates; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceImpl.java index ba4cf06c1c..505e7f4f16 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/plugins/base/PluginServiceImpl.java @@ -1,7 +1,9 @@ package com.appsmith.server.plugins.base; +import com.appsmith.server.configurations.CloudServicesConfig; import com.appsmith.server.repositories.PluginRepository; import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.ConfigService; import com.appsmith.server.services.WorkspaceService; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.validation.Validator; @@ -23,7 +25,9 @@ public class PluginServiceImpl extends PluginServiceCEImpl implements PluginServ PluginManager pluginManager, ReactiveRedisTemplate reactiveTemplate, ChannelTopic topic, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + CloudServicesConfig cloudServicesConfig, + ConfigService configService) { super( validator, @@ -33,6 +37,8 @@ public class PluginServiceImpl extends PluginServiceCEImpl implements PluginServ pluginManager, reactiveTemplate, topic, - objectMapper); + objectMapper, + cloudServicesConfig, + configService); } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/PluginServiceCEImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/PluginServiceCEImplTest.java index cb17b8cbf9..d7eb6147fa 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/PluginServiceCEImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/PluginServiceCEImplTest.java @@ -1,10 +1,12 @@ package com.appsmith.server.services.ce; +import com.appsmith.server.configurations.CloudServicesConfig; import com.appsmith.server.domains.Plugin; import com.appsmith.server.plugins.base.PluginServiceCE; import com.appsmith.server.plugins.base.PluginServiceCEImpl; import com.appsmith.server.repositories.PluginRepository; import com.appsmith.server.services.AnalyticsService; +import com.appsmith.server.services.ConfigService; import com.appsmith.server.services.WorkspaceService; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.validation.Validator; @@ -56,6 +58,8 @@ public class PluginServiceCEImplTest { ObjectMapper objectMapper; PluginServiceCE pluginService; + CloudServicesConfig cloudServicesConfig; + ConfigService configService; @BeforeEach public void setUp() { @@ -68,7 +72,9 @@ public class PluginServiceCEImplTest { pluginManager, reactiveTemplate, topic, - objectMapper); + objectMapper, + cloudServicesConfig, + configService); } @Test