diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/builder/EmailNotificationBuilder.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/builder/EmailNotificationBuilder.java index ee330670d10..d2b75d571bb 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/builder/EmailNotificationBuilder.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/builder/EmailNotificationBuilder.java @@ -102,6 +102,7 @@ public enum EmailTemplate { "Subscription for ${api.name} with plan ${plan.name} has been resumed" ), API_SUBSCRIPTION_REJECTED(ApiHook.SUBSCRIPTION_REJECTED, "subscriptionRejected.html", "Subscription rejected"), + API_SUBSCRIPTION_FAILED(ApiHook.SUBSCRIPTION_FAILED, "subscriptionFailed.html", "Subscription failed"), API_SUBSCRIPTION_TRANSFERRED( ApiHook.SUBSCRIPTION_TRANSFERRED, "subscriptionTransferred.html", @@ -149,6 +150,11 @@ public enum EmailTemplate { "subscriptionRejected.html", "Your subscription to ${api.name} with plan ${plan.name} has been rejected" ), + APPLICATION_SUBSCRIPTION_FAILED( + ApplicationHook.SUBSCRIPTION_FAILED, + "subscriptionFailed.html", + "Your subscription to ${api.name} with plan ${plan.name} has failed" + ), APPLICATION_SUBSCRIPTION_TRANSFERRED( ApplicationHook.SUBSCRIPTION_TRANSFERRED, "subscriptionTransferred.html", diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/SubscriptionServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/SubscriptionServiceImpl.java index 8ff89e82c04..976aa6af11c 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/SubscriptionServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/SubscriptionServiceImpl.java @@ -61,6 +61,7 @@ import io.gravitee.rest.api.model.ApiKeyEntity; import io.gravitee.rest.api.model.ApiKeyMode; import io.gravitee.rest.api.model.ApplicationEntity; +import io.gravitee.rest.api.model.EnvironmentEntity; import io.gravitee.rest.api.model.NewSubscriptionEntity; import io.gravitee.rest.api.model.PageEntity; import io.gravitee.rest.api.model.PrimaryOwnerEntity; @@ -89,6 +90,7 @@ import io.gravitee.rest.api.service.ApiKeyService; import io.gravitee.rest.api.service.ApplicationService; import io.gravitee.rest.api.service.AuditService; +import io.gravitee.rest.api.service.EnvironmentService; import io.gravitee.rest.api.service.GroupService; import io.gravitee.rest.api.service.NotifierService; import io.gravitee.rest.api.service.PageService; @@ -228,6 +230,9 @@ public class SubscriptionServiceImpl extends AbstractService implements Subscrip @Autowired private ObjectMapper objectMapper; + @Autowired + private EnvironmentService environmentService; + @Override public SubscriptionEntity findById(String subscriptionId) { try { @@ -890,6 +895,23 @@ public SubscriptionEntity fail(String subscriptionId, String failureCause) { subscription = subscriptionRepository.update(subscription); + // send notification to subscriber + EnvironmentEntity environmentEntity = environmentService.findById(subscription.getEnvironmentId()); + ExecutionContext executionContext = new ExecutionContext(environmentEntity.getOrganizationId(), environmentEntity.getId()); + final ApplicationEntity application = applicationService.findById(executionContext, subscription.getApplication()); + final GenericPlanEntity genericPlanEntity = planSearchService.findById(executionContext, subscription.getPlan()); + String apiId = genericPlanEntity.getApiId(); + final GenericApiModel genericApiModel = apiTemplateService.findByIdForTemplates(executionContext, apiId); + final PrimaryOwnerEntity owner = application.getPrimaryOwner(); + final Map params = new NotificationParamsBuilder() + .owner(owner) + .api(genericApiModel) + .plan(genericPlanEntity) + .application(application) + .build(); + notifierService.trigger(executionContext, ApiHook.SUBSCRIPTION_FAILED, apiId, params); + notifierService.trigger(executionContext, ApplicationHook.SUBSCRIPTION_FAILED, application.getId(), params); + return convert(subscription); } catch (TechnicalException ex) { throw new TechnicalManagementException( diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notification/ApiHook.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notification/ApiHook.java index 25fc9ccea5a..6316f15477b 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notification/ApiHook.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notification/ApiHook.java @@ -30,6 +30,7 @@ public enum ApiHook implements Hook { SUBSCRIPTION_RESUMED("Subscription Resumed", "Triggered when a Subscription is resumed.", "SUBSCRIPTION"), SUBSCRIPTION_REJECTED("Subscription Rejected", "Triggered when a Subscription is rejected.", "SUBSCRIPTION"), SUBSCRIPTION_TRANSFERRED("Subscription Transferred", "Triggered when a Subscription is transferred.", "SUBSCRIPTION"), + SUBSCRIPTION_FAILED("Subscription Failed", "Triggered when a Subscription fails.", "SUBSCRIPTION"), NEW_SUPPORT_TICKET("New Support Ticket", "Triggered when a new support ticket is created", "SUPPORT"), API_STARTED("API Started", "Triggered when an API is started", "LIFECYCLE"), API_STOPPED("API Stopped", "Triggered when an API is stopped", "LIFECYCLE"), diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notification/ApplicationHook.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notification/ApplicationHook.java index 6c85e10d851..d3b700dd0f2 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notification/ApplicationHook.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notification/ApplicationHook.java @@ -27,6 +27,7 @@ public enum ApplicationHook implements Hook { SUBSCRIPTION_RESUMED("Subscription Resumed", "Triggered when a Subscription is resumed.", "SUBSCRIPTION"), SUBSCRIPTION_REJECTED("Subscription Rejected", "Triggered when a Subscription is rejected.", "SUBSCRIPTION"), SUBSCRIPTION_TRANSFERRED("Subscription Transferred", "Triggered when a Subscription is transferred.", "SUBSCRIPTION"), + SUBSCRIPTION_FAILED("Subscription Failed", "Triggered when a Subscription fails.", "SUBSCRIPTION"), NEW_SUPPORT_TICKET("New Support Ticket", "Triggered when a new support ticket is created", "SUPPORT"); private String label; diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notifiers/impl/WebhookNotifierServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notifiers/impl/WebhookNotifierServiceImpl.java index abb83812969..70385eddefd 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notifiers/impl/WebhookNotifierServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/notifiers/impl/WebhookNotifierServiceImpl.java @@ -86,7 +86,16 @@ private String toJson(final Hook hook, final Map params) { addJsonObject(params, PARAM_OWNER, content, "owner", PrimaryOwnerEntity.class, PrimaryOwnerNotificationTemplateData.class); - addJsonObject(params, PARAM_PLAN, content, "plan", PlanEntity.class, PlanNotificationTemplateData.class); + addJsonObject( + params, + PARAM_PLAN, + content, + "plan", + PlanEntity.class, + io.gravitee.rest.api.model.v4.plan.PlanEntity.class, + PlanNotificationTemplateData.class, + BasePlanEntity.class + ); addJsonObject( params, @@ -153,6 +162,16 @@ private void populateJson(JsonObject jsonObject, Object object, Class dataTyp jsonObject.put("id", plan.getId()); jsonObject.put("name", plan.getName()); jsonObject.put("security", plan.getSecurity()); + } else if (dataType == io.gravitee.rest.api.model.v4.plan.PlanEntity.class) { + io.gravitee.rest.api.model.v4.plan.PlanEntity plan = (io.gravitee.rest.api.model.v4.plan.PlanEntity) object; + jsonObject.put("id", plan.getId()); + jsonObject.put("name", plan.getName()); + jsonObject.put("security", plan.getSecurity()); + } else if (dataType == BasePlanEntity.class) { + BasePlanEntity plan = (BasePlanEntity) object; + jsonObject.put("id", plan.getId()); + jsonObject.put("name", plan.getName()); + jsonObject.put("security", plan.getSecurity()); } else if (dataType == PlanNotificationTemplateData.class) { PlanNotificationTemplateData notificationData = (PlanNotificationTemplateData) object; jsonObject.put("id", notificationData.getId()); diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/api/use_case/CreateHttpApiUseCaseTest.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/api/use_case/CreateHttpApiUseCaseTest.java index ac744486b70..4d1916655b0 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/api/use_case/CreateHttpApiUseCaseTest.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/api/use_case/CreateHttpApiUseCaseTest.java @@ -423,6 +423,7 @@ void should_create_default_email_notification_configuration() { "REVIEW_OK", "SUBSCRIPTION_ACCEPTED", "SUBSCRIPTION_CLOSED", + "SUBSCRIPTION_FAILED", "SUBSCRIPTION_NEW", "SUBSCRIPTION_PAUSED", "SUBSCRIPTION_REJECTED", diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/api/use_case/CreateNativeApiUseCaseTest.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/api/use_case/CreateNativeApiUseCaseTest.java index df4ba85dca6..36cadf44b53 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/api/use_case/CreateNativeApiUseCaseTest.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/api/use_case/CreateNativeApiUseCaseTest.java @@ -410,6 +410,7 @@ void should_create_default_email_notification_configuration() { "REVIEW_OK", "SUBSCRIPTION_ACCEPTED", "SUBSCRIPTION_CLOSED", + "SUBSCRIPTION_FAILED", "SUBSCRIPTION_NEW", "SUBSCRIPTION_PAUSED", "SUBSCRIPTION_REJECTED", diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/rest/api/service/impl/SubscriptionServiceTest.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/rest/api/service/impl/SubscriptionServiceTest.java index 2716cbf950a..b2cfd73ca0e 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/rest/api/service/impl/SubscriptionServiceTest.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/rest/api/service/impl/SubscriptionServiceTest.java @@ -37,6 +37,8 @@ import static org.mockito.Mockito.anyMap; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.argThat; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -68,6 +70,7 @@ import io.gravitee.rest.api.model.ApiKeyMode; import io.gravitee.rest.api.model.ApiModel; import io.gravitee.rest.api.model.ApplicationEntity; +import io.gravitee.rest.api.model.EnvironmentEntity; import io.gravitee.rest.api.model.GroupEntity; import io.gravitee.rest.api.model.NewSubscriptionEntity; import io.gravitee.rest.api.model.PageEntity; @@ -92,9 +95,11 @@ import io.gravitee.rest.api.model.subscription.SubscriptionMetadataQuery; import io.gravitee.rest.api.model.subscription.SubscriptionQuery; import io.gravitee.rest.api.model.v4.api.GenericApiEntity; +import io.gravitee.rest.api.model.v4.plan.GenericPlanEntity; import io.gravitee.rest.api.service.ApiKeyService; import io.gravitee.rest.api.service.ApplicationService; import io.gravitee.rest.api.service.AuditService; +import io.gravitee.rest.api.service.EnvironmentService; import io.gravitee.rest.api.service.GroupService; import io.gravitee.rest.api.service.NotifierService; import io.gravitee.rest.api.service.PageService; @@ -126,10 +131,12 @@ import io.gravitee.rest.api.service.v4.validation.SubscriptionValidationService; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.Arrays; import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -150,6 +157,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -242,6 +250,9 @@ public class SubscriptionServiceTest { @Mock private RejectSubscriptionDomainService rejectSubscriptionDomainService; + @Mock + private EnvironmentService environmentService; + @Spy private ObjectMapper objectMapper = new ObjectMapper(); @@ -1107,8 +1118,24 @@ public void shouldFailSubscription() throws TechnicalException { final Date initialUpdateDate = new Date(yesterday); subscription.setUpdatedAt(initialUpdateDate); + EnvironmentEntity environmentEntity = new EnvironmentEntity(); + environmentEntity.setId("DEFAULT"); + environmentEntity.setOrganizationId("DEFAULT"); + + PlanEntity planEntity = new PlanEntity(); + planEntity.setId("A"); + planEntity.setStatus(PlanStatus.PUBLISHED); + + ApplicationEntity applicationEntity = mock(ApplicationEntity.class); + + PrimaryOwnerEntity primaryOwnerEntity = mock(PrimaryOwnerEntity.class); + when(subscriptionRepository.findById(SUBSCRIPTION_ID)).thenReturn(Optional.of(subscription)); when(subscriptionRepository.update(subscription)).thenReturn(subscription); + when(environmentService.findById(any())).thenReturn(environmentEntity); + when(planSearchService.findById(GraviteeContext.getExecutionContext(), PLAN_ID)).thenReturn(planEntity); + when(applicationService.findById(any(), any())).thenReturn(applicationEntity); + when(applicationEntity.getPrimaryOwner()).thenReturn(primaryOwnerEntity); final String failureCause = "💥 Endpoint not available"; subscriptionService.fail(SUBSCRIPTION_ID, failureCause); @@ -1122,6 +1149,10 @@ public void shouldFailSubscription() throws TechnicalException { assertThat(subscriptionCaptured.getConsumerStatus()).isEqualTo(Subscription.ConsumerStatus.FAILURE); assertThat(subscriptionCaptured.getFailureCause()).isEqualTo(failureCause); assertThat(subscriptionCaptured.getUpdatedAt()).isAfter(initialUpdateDate); + verify(notifierService) + .trigger(eq(GraviteeContext.getExecutionContext()), eq(ApiHook.SUBSCRIPTION_FAILED), nullable(String.class), anyMap()); + verify(notifierService) + .trigger(eq(GraviteeContext.getExecutionContext()), eq(ApplicationHook.SUBSCRIPTION_FAILED), nullable(String.class), anyMap()); } @Test diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/notifications/portal/API.SUBSCRIPTION_FAILED.yml b/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/notifications/portal/API.SUBSCRIPTION_FAILED.yml new file mode 100644 index 00000000000..0712d6b6572 --- /dev/null +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/notifications/portal/API.SUBSCRIPTION_FAILED.yml @@ -0,0 +1,5 @@ +title: | + [${api.name}] Subscription Failed +message: | + The subscription request for the application "${application.name}" + to the plan "${plan.name}" has failed. \ No newline at end of file diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/notifications/portal/APPLICATION.SUBSCRIPTION_FAILED.yml b/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/notifications/portal/APPLICATION.SUBSCRIPTION_FAILED.yml new file mode 100644 index 00000000000..dd95a33dd82 --- /dev/null +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/notifications/portal/APPLICATION.SUBSCRIPTION_FAILED.yml @@ -0,0 +1,5 @@ +title: | + [${application.name}] Subscription Failed +message: | + The subscription request to the plan "${plan.name}" + of the api "${api.name}" has failed. \ No newline at end of file diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/subscriptionFailed.html b/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/subscriptionFailed.html new file mode 100644 index 00000000000..a9debca1dca --- /dev/null +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-standalone/gravitee-apim-rest-api-standalone-distribution/src/main/resources/templates/subscriptionFailed.html @@ -0,0 +1,16 @@ + + +
+ <#include "header.html" /> +
+
+

Hi,

+

The subscription to the API ${api.name} with plan ${plan.name} has failed.

+ + <#if subscription.reason??> + Message +

${subscription.reason}

+ +
+ +