killbill-memoizeit

analytics: add API to retrieve the number of subscriptions

8/23/2012 12:22:52 PM

Details

diff --git a/analytics/src/main/java/com/ning/billing/analytics/api/user/DefaultAnalyticsUserApi.java b/analytics/src/main/java/com/ning/billing/analytics/api/user/DefaultAnalyticsUserApi.java
index cbd652d..691eb33 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/api/user/DefaultAnalyticsUserApi.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/api/user/DefaultAnalyticsUserApi.java
@@ -45,6 +45,11 @@ public class DefaultAnalyticsUserApi implements AnalyticsUserApi {
         return analyticsDao.getAccountsCreatedOverTime();
     }
 
+    @Override
+    public TimeSeriesData getSubscriptionsCreatedOverTime(final String productType, final String slug) {
+        return analyticsDao.getSubscriptionsCreatedOverTime(productType, slug);
+    }
+
     // Note: the following is not exposed in api yet, as the models need to be extracted first
 
     public BusinessAccount getAccountByKey(final String accountKey) {
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
index d4ae76e..165d087 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/AnalyticsDao.java
@@ -31,6 +31,8 @@ public interface AnalyticsDao {
 
     TimeSeriesData getAccountsCreatedOverTime();
 
+    TimeSeriesData getSubscriptionsCreatedOverTime(final String productType, final String slug);
+
     BusinessAccount getAccountByKey(final String accountKey);
 
     List<BusinessSubscriptionTransition> getTransitionsByKey(final String externalKey);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountSqlDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountSqlDao.java
index d2c6d2e..b538d04 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountSqlDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/BusinessAccountSqlDao.java
@@ -36,6 +36,10 @@ public interface BusinessAccountSqlDao extends Transactional<BusinessAccountSqlD
     List<TimeSeriesTuple> getAccountsCreatedOverTime();
 
     @SqlQuery
+    List<TimeSeriesTuple> getSubscriptionsCreatedOverTime(@Bind("product_type") final String productType,
+                                                          @Bind("slug") final String slug);
+
+    @SqlQuery
     BusinessAccount getAccount(@Bind("account_id") final String accountId);
 
     @SqlQuery
diff --git a/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
index 0918673..fe4fe6e 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/dao/DefaultAnalyticsDao.java
@@ -63,6 +63,11 @@ public class DefaultAnalyticsDao implements AnalyticsDao {
     }
 
     @Override
+    public TimeSeriesData getSubscriptionsCreatedOverTime(final String productType, final String slug) {
+        return new DefaultTimeSeriesData(accountSqlDao.getSubscriptionsCreatedOverTime(productType, slug));
+    }
+
+    @Override
     public BusinessAccount getAccountByKey(final String accountKey) {
         return accountSqlDao.getAccountByKey(accountKey);
     }
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscription.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscription.java
index 86b6244..18d85cc 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscription.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscription.java
@@ -41,6 +41,7 @@ import static com.ning.billing.entitlement.api.user.Subscription.SubscriptionSta
  * Describe a subscription for Analytics purposes
  */
 public class BusinessSubscription {
+
     private static final Logger log = LoggerFactory.getLogger(BusinessSubscription.class);
 
     private static final BigDecimal DAYS_IN_MONTH = BigDecimal.valueOf(30);
diff --git a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionEvent.java b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionEvent.java
index b529559..eac9c07 100644
--- a/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionEvent.java
+++ b/analytics/src/main/java/com/ning/billing/analytics/model/BusinessSubscriptionEvent.java
@@ -32,6 +32,7 @@ import static com.ning.billing.entitlement.api.user.Subscription.SubscriptionSta
  * Describe an event associated with a transition between two BusinessSubscription
  */
 public class BusinessSubscriptionEvent {
+
     private static final Logger log = LoggerFactory.getLogger(BusinessSubscriptionEvent.class);
 
     private static final String MISC = "MISC";
diff --git a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountSqlDao.sql.stg b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountSqlDao.sql.stg
index c6b9195..a253aea 100644
--- a/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountSqlDao.sql.stg
+++ b/analytics/src/main/resources/com/ning/billing/analytics/dao/BusinessAccountSqlDao.sql.stg
@@ -11,6 +11,19 @@ getAccountsCreatedOverTime() ::= <<
   ;
 >>
 
+getSubscriptionsCreatedOverTime(product_type, slug) ::= <<
+  select
+    date(from_unixtime(requested_timestamp / 1000)) day
+  , count(record_id) count
+  from bst
+  where event in ('ADD_ADD_ON', 'ADD_BASE', 'ADD_STANDALONE')
+  and next_product_type = :product_type
+  and next_slug = :slug
+  group by 1
+  order by 1
+  ;
+>>
+
 getAccount(account_id) ::= <<
   select
     account_id
diff --git a/analytics/src/test/java/com/ning/billing/analytics/api/user/TestDefaultAnalyticsUserApi.java b/analytics/src/test/java/com/ning/billing/analytics/api/user/TestDefaultAnalyticsUserApi.java
index 1b7ef04..45ef9b9 100644
--- a/analytics/src/test/java/com/ning/billing/analytics/api/user/TestDefaultAnalyticsUserApi.java
+++ b/analytics/src/test/java/com/ning/billing/analytics/api/user/TestDefaultAnalyticsUserApi.java
@@ -19,13 +19,18 @@ package com.ning.billing.analytics.api.user;
 import java.math.BigDecimal;
 import java.util.UUID;
 
+import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
+import org.mockito.Mockito;
 import org.skife.jdbi.v2.IDBI;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.ning.billing.analytics.AnalyticsTestSuiteWithEmbeddedDB;
+import com.ning.billing.analytics.MockDuration;
+import com.ning.billing.analytics.MockPhase;
+import com.ning.billing.analytics.MockProduct;
 import com.ning.billing.analytics.api.TimeSeriesData;
 import com.ning.billing.analytics.dao.AnalyticsDao;
 import com.ning.billing.analytics.dao.BusinessAccountSqlDao;
@@ -37,6 +42,18 @@ import com.ning.billing.analytics.dao.BusinessOverdueStatusSqlDao;
 import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
 import com.ning.billing.analytics.dao.DefaultAnalyticsDao;
 import com.ning.billing.analytics.model.BusinessAccount;
+import com.ning.billing.analytics.model.BusinessSubscription;
+import com.ning.billing.analytics.model.BusinessSubscriptionEvent;
+import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
+import com.ning.billing.catalog.api.Catalog;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PhaseType;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.PlanPhase;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.mock.MockPlan;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.clock.ClockMock;
 
@@ -81,4 +98,38 @@ public class TestDefaultAnalyticsUserApi extends AnalyticsTestSuiteWithEmbeddedD
         Assert.assertEquals(data.getValues().size(), 1);
         Assert.assertEquals(data.getValues().get(0), (double) 1);
     }
+
+    @Test(groups = "slow")
+    public void testSubscriptionsCreatedOverTime() throws Exception {
+        final String productType = "subscription";
+        final Product product = new MockProduct("platinum", productType, ProductCategory.BASE);
+        final Plan plan = new MockPlan("platinum-monthly", product);
+        final PlanPhase phase = new MockPhase(PhaseType.TRIAL, plan, MockDuration.UNLIMITED(), 25.95);
+        final Catalog catalog = Mockito.mock(Catalog.class);
+        Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(plan);
+        Mockito.when(catalog.findPhase(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(phase);
+        final BusinessSubscriptionTransition transition = new BusinessSubscriptionTransition(
+                3L,
+                UUID.randomUUID(),
+                UUID.randomUUID().toString(),
+                UUID.randomUUID(),
+                UUID.randomUUID().toString(),
+                UUID.randomUUID(),
+                clock.getUTCNow(),
+                BusinessSubscriptionEvent.subscriptionCreated(plan.getName(), catalog, clock.getUTCNow(), clock.getUTCNow()),
+                null,
+                new BusinessSubscription("DEFAULT", plan.getName(), phase.getName(), Currency.USD, clock.getUTCNow(), Subscription.SubscriptionState.ACTIVE, catalog)
+        );
+        subscriptionTransitionSqlDao.createTransition(transition);
+
+        final TimeSeriesData notFoundData = analyticsUserApi.getSubscriptionsCreatedOverTime(productType, UUID.randomUUID().toString());
+        Assert.assertEquals(notFoundData.getDates().size(), 0);
+        Assert.assertEquals(notFoundData.getValues().size(), 0);
+
+        final TimeSeriesData data = analyticsUserApi.getSubscriptionsCreatedOverTime(productType, phase.getName());
+        Assert.assertEquals(data.getDates().size(), 1);
+        Assert.assertEquals(data.getDates().get(0), new LocalDate());
+        Assert.assertEquals(data.getValues().size(), 1);
+        Assert.assertEquals(data.getValues().get(0), (double) 1);
+    }
 }
diff --git a/api/src/main/java/com/ning/billing/analytics/api/user/AnalyticsUserApi.java b/api/src/main/java/com/ning/billing/analytics/api/user/AnalyticsUserApi.java
index 26a2480..dc79d9a 100644
--- a/api/src/main/java/com/ning/billing/analytics/api/user/AnalyticsUserApi.java
+++ b/api/src/main/java/com/ning/billing/analytics/api/user/AnalyticsUserApi.java
@@ -24,4 +24,11 @@ public interface AnalyticsUserApi {
      * @return the number of accounts created per day
      */
     public TimeSeriesData getAccountsCreatedOverTime();
+
+    /**
+     * @param productType catalog name
+     * @param slug        plan phase name, as returned by PlanPhase#getName()
+     * @return the number of new subscriptions created per day (transfers not included)
+     */
+    public TimeSeriesData getSubscriptionsCreatedOverTime(final String productType, final String slug);
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AnalyticsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AnalyticsResource.java
index fbc3623..f0df8ac 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AnalyticsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AnalyticsResource.java
@@ -20,6 +20,7 @@ import javax.inject.Inject;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
@@ -50,4 +51,14 @@ public class AnalyticsResource {
         final TimeSeriesDataJson json = new TimeSeriesDataJson(data);
         return Response.status(Status.OK).entity(json).build();
     }
+
+    @GET
+    @Path("/subscriptionsCreatedOverTime")
+    @Produces(APPLICATION_JSON)
+    public Response getSubscriptionsCreatedOverTime(@QueryParam("productType") final String productType,
+                                                    @QueryParam("slug") final String slug) {
+        final TimeSeriesData data = analyticsUserApi.getSubscriptionsCreatedOverTime(productType, slug);
+        final TimeSeriesDataJson json = new TimeSeriesDataJson(data);
+        return Response.status(Status.OK).entity(json).build();
+    }
 }