killbill-uncached
Changes
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/http/AnalyticsServlet.java 7(+6 -1)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/json/XY.java 7(+7 -0)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/AverageSmoother.java 34(+34 -0)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/DateGranularity.java 22(+22 -0)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/Smoother.java 134(+134 -0)
osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/SummingSmoother.java 34(+34 -0)
Details
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/http/AnalyticsServlet.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/http/AnalyticsServlet.java
index fbef095..6fbcf1a 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/http/AnalyticsServlet.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/http/AnalyticsServlet.java
@@ -39,6 +39,8 @@ import com.ning.billing.osgi.bundles.analytics.api.BusinessSnapshot;
import com.ning.billing.osgi.bundles.analytics.api.user.AnalyticsUserApi;
import com.ning.billing.osgi.bundles.analytics.json.NamedXYTimeSeries;
import com.ning.billing.osgi.bundles.analytics.reports.ReportsUserApi;
+import com.ning.billing.osgi.bundles.analytics.reports.analysis.Smoother;
+import com.ning.billing.osgi.bundles.analytics.reports.analysis.Smoother.SmootherType;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallOrigin;
import com.ning.billing.util.callcontext.UserType;
@@ -69,6 +71,7 @@ public class AnalyticsServlet extends HttpServlet {
private static final String REPORTS = "reports";
private static final String REPORTS_QUERY_NAME = "name";
+ private static final String REPORTS_SMOOTHER_NAME = "smooth";
private static final ObjectMapper mapper = ObjectMapperProvider.get();
@@ -179,8 +182,10 @@ public class AnalyticsServlet extends HttpServlet {
final LocalDate startDate = Strings.emptyToNull(req.getParameter(QUERY_START_DATE)) != null ? DATE_FORMAT.parseLocalDate(req.getParameter(QUERY_START_DATE)) : null;
final LocalDate endDate = Strings.emptyToNull(req.getParameter(QUERY_END_DATE)) != null ? DATE_FORMAT.parseLocalDate(req.getParameter(QUERY_END_DATE)) : null;
+ final SmootherType smootherType = Smoother.fromString(Strings.emptyToNull(req.getParameter(REPORTS_SMOOTHER_NAME)));
+
// TODO PIERRE Switch to an equivalent of StreamingOutputStream?
- final List<NamedXYTimeSeries> result = reportsUserApi.getTimeSeriesDataForReport(reportNames, startDate, endDate);
+ final List<NamedXYTimeSeries> result = reportsUserApi.getTimeSeriesDataForReport(reportNames, startDate, endDate, smootherType);
resp.getOutputStream().write(mapper.writeValueAsBytes(result));
resp.setContentType("application/json");
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/json/XY.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/json/XY.java
index 638a597..bd5ed23 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/json/XY.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/json/XY.java
@@ -18,8 +18,10 @@ package com.ning.billing.osgi.bundles.analytics.json;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
public class XY {
@@ -46,10 +48,15 @@ public class XY {
this.xDate = xDate;
}
+ public XY(final LocalDate xDate, final Float y) {
+ this(xDate.toDateTimeAtStartOfDay(DateTimeZone.UTC), y);
+ }
+
public String getX() {
return x;
}
+ @JsonIgnore
public DateTime getxDate() {
return xDate;
}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/AverageSmoother.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/AverageSmoother.java
new file mode 100644
index 0000000..aaff67b
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/AverageSmoother.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.osgi.bundles.analytics.reports.analysis;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ning.billing.osgi.bundles.analytics.json.XY;
+
+public class AverageSmoother extends Smoother {
+
+ public AverageSmoother(final Map<String, Map<String, List<XY>>> dataForReports, final DateGranularity dateGranularity) {
+ super(dataForReports, dateGranularity);
+ }
+
+ @Override
+ public float computeSmoothedValue(final float accumulator, final int accumulatorSize) {
+ return accumulator / accumulatorSize;
+ }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/DateGranularity.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/DateGranularity.java
new file mode 100644
index 0000000..89579e8
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/DateGranularity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.osgi.bundles.analytics.reports.analysis;
+
+public enum DateGranularity {
+ WEEKLY,
+ MONTHLY
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/Smoother.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/Smoother.java
new file mode 100644
index 0000000..6ce8a8e
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/Smoother.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.osgi.bundles.analytics.reports.analysis;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTimeConstants;
+import org.joda.time.LocalDate;
+
+import com.ning.billing.osgi.bundles.analytics.json.XY;
+
+import com.google.common.base.Function;
+
+public abstract class Smoother {
+
+ private final Map<String, Map<String, List<XY>>> dataForReports;
+ private final DateGranularity dateGranularity;
+
+ public static enum SmootherType {
+ AVERAGE_WEEKLY,
+ AVERAGE_MONTHLY,
+ SUM_WEEKLY,
+ SUM_MONTHLY;
+
+ public Smoother createSmoother(final Map<String, Map<String, List<XY>>> dataForReports) {
+ switch (this) {
+ case AVERAGE_WEEKLY:
+ return new AverageSmoother(dataForReports, DateGranularity.WEEKLY);
+ case AVERAGE_MONTHLY:
+ return new AverageSmoother(dataForReports, DateGranularity.MONTHLY);
+ case SUM_WEEKLY:
+ return new SummingSmoother(dataForReports, DateGranularity.WEEKLY);
+ case SUM_MONTHLY:
+ return new SummingSmoother(dataForReports, DateGranularity.MONTHLY);
+ default:
+ return null;
+ }
+ }
+ }
+
+ public static SmootherType fromString(@Nullable final String smootherName) {
+ if (smootherName == null) {
+ return null;
+ } else {
+ return SmootherType.valueOf(smootherName.toUpperCase());
+ }
+ }
+
+ public Smoother(final Map<String, Map<String, List<XY>>> dataForReports, final DateGranularity dateGranularity) {
+ this.dataForReports = dataForReports;
+ this.dateGranularity = dateGranularity;
+ }
+
+ public abstract float computeSmoothedValue(float accumulator, int accumulatorSize);
+
+ // Assume the data is already sorted
+ public void smooth() {
+ for (final Map<String, List<XY>> dataForReport : dataForReports.values()) {
+ for (final String pivotName : dataForReport.keySet()) {
+ final List<XY> dataForPivot = dataForReport.get(pivotName);
+ final List<XY> smoothedData = smooth(dataForPivot);
+ dataForReport.put(pivotName, smoothedData);
+ }
+ }
+ }
+
+ public Map<String, Map<String, List<XY>>> getDataForReports() {
+ return dataForReports;
+ }
+
+ private List<XY> smooth(final List<XY> inputData) {
+ switch (dateGranularity) {
+ case WEEKLY:
+ return smooth(inputData,
+ new Function<XY, LocalDate>() {
+ @Override
+ public LocalDate apply(final XY input) {
+ return input.getxDate().toLocalDate().withDayOfWeek(DateTimeConstants.MONDAY);
+ }
+ });
+ case MONTHLY:
+ return smooth(inputData,
+ new Function<XY, LocalDate>() {
+ @Override
+ public LocalDate apply(final XY input) {
+ return input.getxDate().toLocalDate().withDayOfMonth(1);
+ }
+ });
+ default:
+ return inputData;
+ }
+ }
+
+ private List<XY> smooth(final List<XY> inputData, final Function<XY, LocalDate> truncator) {
+ final List<XY> smoothedData = new LinkedList<XY>();
+
+ LocalDate currentTruncatedDate = truncator.apply(inputData.get(0));
+ Float accumulator = (float) 0;
+ int accumulatorSize = 0;
+ for (final XY xy : inputData) {
+ final LocalDate zeTruncatedDate = truncator.apply(xy);
+ //noinspection ConstantConditions
+ if (zeTruncatedDate.compareTo(currentTruncatedDate) != 0) {
+ smoothedData.add(new XY(currentTruncatedDate, computeSmoothedValue(accumulator, accumulatorSize)));
+ accumulator = (float) 0;
+ accumulatorSize = 0;
+ }
+
+ accumulator += xy.getY();
+ accumulatorSize++;
+ currentTruncatedDate = zeTruncatedDate;
+ }
+
+ return smoothedData;
+ }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/SummingSmoother.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/SummingSmoother.java
new file mode 100644
index 0000000..890979c
--- /dev/null
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/analysis/SummingSmoother.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.osgi.bundles.analytics.reports.analysis;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ning.billing.osgi.bundles.analytics.json.XY;
+
+public class SummingSmoother extends Smoother {
+
+ public SummingSmoother(final Map<String, Map<String, List<XY>>> dataForReports, final DateGranularity dateGranularity) {
+ super(dataForReports, dateGranularity);
+ }
+
+ @Override
+ public float computeSmoothedValue(final float accumulator, final int accumulatorSize) {
+ return accumulator;
+ }
+}
diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/ReportsUserApi.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/ReportsUserApi.java
index f7b18d4..cc0b89c 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/ReportsUserApi.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/reports/ReportsUserApi.java
@@ -39,6 +39,8 @@ import com.ning.billing.osgi.bundles.analytics.BusinessExecutor;
import com.ning.billing.osgi.bundles.analytics.dao.BusinessDBIProvider;
import com.ning.billing.osgi.bundles.analytics.json.NamedXYTimeSeries;
import com.ning.billing.osgi.bundles.analytics.json.XY;
+import com.ning.billing.osgi.bundles.analytics.reports.analysis.Smoother;
+import com.ning.billing.osgi.bundles.analytics.reports.analysis.Smoother.SmootherType;
import com.ning.killbill.osgi.libs.killbill.OSGIKillbillDataSource;
import com.google.common.base.Predicate;
@@ -67,7 +69,8 @@ public class ReportsUserApi {
public List<NamedXYTimeSeries> getTimeSeriesDataForReport(final String[] reportNames,
@Nullable final LocalDate startDate,
- @Nullable final LocalDate endDate) {
+ @Nullable final LocalDate endDate,
+ @Nullable final SmootherType smootherType) {
// Mapping of report name -> pivots -> data
final Map<String, Map<String, List<XY>>> dataForReports = new ConcurrentHashMap<String, Map<String, List<XY>>>();
@@ -77,10 +80,20 @@ public class ReportsUserApi {
// Filter the data first
filterValues(dataForReports, startDate, endDate);
- // Normalize the data
- normalizeXValues(dataForReports, startDate, endDate);
+ // Normalize and sort the data
+ normalizeAndSortXValues(dataForReports, startDate, endDate);
- // Build the named timeseries
+ // Smooth the data if needed and build the named timeseries
+ if (smootherType != null) {
+ final Smoother smoother = smootherType.createSmoother(dataForReports);
+ smoother.smooth();
+ return buildNamedXYTimeSeries(smoother.getDataForReports());
+ } else {
+ return buildNamedXYTimeSeries(dataForReports);
+ }
+ }
+
+ private List<NamedXYTimeSeries> buildNamedXYTimeSeries(final Map<String, Map<String, List<XY>>> dataForReports) {
final List<NamedXYTimeSeries> results = new LinkedList<NamedXYTimeSeries>();
for (final String reportName : dataForReports.keySet()) {
// Sort the pivots by name for a consistent display in the dashboard
@@ -146,7 +159,7 @@ public class ReportsUserApi {
}
// TODO PIERRE Naive implementation
- private void normalizeXValues(final Map<String, Map<String, List<XY>>> dataForReports, @Nullable final LocalDate startDate, @Nullable final LocalDate endDate) {
+ private void normalizeAndSortXValues(final Map<String, Map<String, List<XY>>> dataForReports, @Nullable final LocalDate startDate, @Nullable final LocalDate endDate) {
DateTime minDate = null;
if (startDate != null) {
minDate = startDate.toDateTimeAtStartOfDay(DateTimeZone.UTC);
diff --git a/osgi-bundles/bundles/analytics/src/main/resources/static/analytics.html b/osgi-bundles/bundles/analytics/src/main/resources/static/analytics.html
index c1a76ba..c4759e2 100644
--- a/osgi-bundles/bundles/analytics/src/main/resources/static/analytics.html
+++ b/osgi-bundles/bundles/analytics/src/main/resources/static/analytics.html
@@ -65,8 +65,11 @@
}
// Get the data for a set of reports
- function doGetData(position, reports, from, to, fn) {
+ function doGetData(position, reports, from, to, smoothFunction, fn) {
var request_url = "http://" + $VAR_SERVER + ":" + $VAR_PORT + "/plugins/killbill-analytics/reports?startDate=" + from + "&endDate=" + to + "&name=" + reports.join("&name=");
+ if (smoothFunction) {
+ request_url = request_url + "&smooth=" + smoothFunction;
+ }
return $.ajax({
type: "GET",
@@ -92,6 +95,8 @@
// Map of position (starting from the top) to an array of reports
var reports = {}
+ // Map of position (starting from the top) to a smoothing function
+ var smoothFunctions = {}
for (var i = 1; i < 10; i++) {
var reportsI = $.url().param('report' + i);
if (!reportsI) {
@@ -102,6 +107,8 @@
} else {
reports[i] = [reportsI];
}
+
+ smoothFunctions[i] = $.url().param('smooth' + i);
}
// Set sane defaults
@@ -116,7 +123,7 @@
var futuresData = {}
for (var position in reports) {
// Fetch the data
- var future = doGetData(position, reports[position], from, to, function(zePosition, reports, reportsData) {
+ var future = doGetData(position, reports[position], from, to, smoothFunctions[position], function(zePosition, reports, reportsData) {
console.log(typeof reportsData);
if (!(reportsData instanceof Array) || reportsData.length == 0) {
futuresData[zePosition] = [ { "name": "No data", "values": [] } ];