/*
* Licensed 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 org.rakam.analysis.retention;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.airlift.log.Logger;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.rakam.ServiceStarter;
import org.rakam.analysis.QueryHttpService;
import org.rakam.analysis.RetentionQueryExecutor;
import org.rakam.analysis.RetentionQueryExecutor.DateUnit;
import org.rakam.analysis.RetentionQueryExecutor.RetentionAction;
import org.rakam.report.QueryExecution;
import org.rakam.report.QueryResult;
import org.rakam.server.http.HttpService;
import org.rakam.server.http.RakamHttpRequest;
import org.rakam.server.http.annotations.Api;
import org.rakam.server.http.annotations.ApiOperation;
import org.rakam.server.http.annotations.ApiParam;
import org.rakam.server.http.annotations.Authorization;
import org.rakam.server.http.annotations.BodyParam;
import org.rakam.server.http.annotations.IgnoreApi;
import org.rakam.server.http.annotations.JsonRequest;
import org.rakam.util.JsonHelper;
import org.rakam.util.RakamException;
import javax.inject.Inject;
import javax.inject.Named;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Path("/retention")
@Api(value = "/retention", nickname = "retentionAnalyzer", tags = "retention")
public class RetentionAnalyzerHttpService
extends HttpService
{
private final RetentionQueryExecutor retentionQueryExecutor;
private final QueryHttpService queryService;
private final static Logger LOGGER = Logger.get(RetentionAnalyzerHttpService.class);
@Inject
public RetentionAnalyzerHttpService(RetentionQueryExecutor retentionQueryExecutor, QueryHttpService queryService)
{
this.retentionQueryExecutor = retentionQueryExecutor;
this.queryService = queryService;
}
@ApiOperation(value = "Execute query",
authorizations = @Authorization(value = "read_key"),
consumes = "text/event-stream",
produces = "text/event-stream"
)
@GET
@IgnoreApi
@Path("/analyze")
public void analyzeRetention(RakamHttpRequest request)
{
queryService.handleServerSentQueryExecution(request, RetentionQuery.class, (project, query) -> {
QueryExecution execution = retentionQueryExecutor.query(project,
Optional.ofNullable(query.firstAction),
Optional.ofNullable(query.returningAction),
query.dateUnit,
Optional.ofNullable(query.dimension),
Optional.ofNullable(query.period),
query.startDate,
query.endDate,
query.timezone,
query.approximate);
execution.getResult().thenAccept(data -> {
if (data.isFailed()) {
LOGGER.error("Error running retention query",
new RuntimeException(JsonHelper.encode(query) + " : " + data.getError().toString()));
}
});
return execution;
});
}
@ApiOperation(value = "Execute query",
authorizations = @Authorization(value = "read_key")
)
@POST
@JsonRequest
@Path("/analyze")
public CompletableFuture<QueryResult> analyzeRetention(@Named("project") String project, @BodyParam RetentionQuery query)
{
CompletableFuture<QueryResult> result = retentionQueryExecutor.query(project,
Optional.ofNullable(query.firstAction),
Optional.ofNullable(query.returningAction),
query.dateUnit,
Optional.ofNullable(query.dimension),
Optional.ofNullable(query.period),
query.startDate,
query.endDate,
query.timezone,
query.approximate).getResult();
result.thenAccept(data -> {
if (data.isFailed()) {
LOGGER.error("Error running retention query",
new RuntimeException(JsonHelper.encode(query) + " : " + data.getError().toString()));
}
});
return result;
}
private static class RetentionQuery
{
private final RetentionAction firstAction;
private final RetentionAction returningAction;
private final DateUnit dateUnit;
private final String dimension;
private final Integer period;
private final LocalDate startDate;
private final LocalDate endDate;
private final ZoneId timezone;
public final boolean approximate;
@JsonCreator
public RetentionQuery(@ApiParam("first_action") RetentionAction firstAction,
@ApiParam("returning_action") RetentionAction returningAction,
@ApiParam("dimension") String dimension,
@ApiParam("date_unit") DateUnit dateUnit,
@ApiParam(value = "period", required = false) Integer period,
@ApiParam("startDate") LocalDate startDate,
@ApiParam(value = "timezone", required = false) String timezone,
@ApiParam(value = "approximate", required = false) Boolean approximate,
@ApiParam("endDate") LocalDate endDate)
{
this.firstAction = firstAction;
this.returningAction = returningAction;
this.dateUnit = dateUnit;
this.dimension = dimension;
this.period = period;
this.startDate = startDate;
this.endDate = endDate;
this.approximate = Boolean.TRUE.equals(approximate);
try {
this.timezone = Optional.ofNullable(timezone)
.map(t -> ZoneId.of(t))
.orElse(ZoneOffset.UTC);
}
catch (Exception e) {
throw new RakamException("Timezone is invalid", HttpResponseStatus.BAD_REQUEST);
}
}
}
}