Analytics data point on action execution (#2740)
* Add analytics data point on action execution * Include application details in action exec data point * Only send action execution event on cloud * Analytics is auto-disabled on self-hosted setups * Move event name to AnalyticsEvents enum * Move analytics Mono to separate method * Use a common function to enqueue analytics message * Provide analytics properties from caller method * Use consistent casing in event names for analytics
This commit is contained in:
parent
11f5687b38
commit
e9ba40f1f4
|
|
@ -60,7 +60,7 @@ public class AuthenticationSuccessHandler implements ServerAuthenticationSuccess
|
|||
.flatMap(user -> {
|
||||
final boolean isFromInvite = user.getInviteToken() != null;
|
||||
return Mono.whenDelayError(
|
||||
analyticsService.sendEvent(AnalyticsEvents.FIRST_LOGIN, user, Map.of("isFromInvite", isFromInvite)),
|
||||
analyticsService.sendObjectEvent(AnalyticsEvents.FIRST_LOGIN, user, Map.of("isFromInvite", isFromInvite)),
|
||||
examplesOrganizationCloner.cloneExamplesOrganization()
|
||||
);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,13 +1,26 @@
|
|||
package com.appsmith.server.constants;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum AnalyticsEvents {
|
||||
CREATE,
|
||||
UPDATE,
|
||||
DELETE,
|
||||
FIRST_LOGIN,
|
||||
EXECUTE_ACTION("execute_ACTION_TRIGGERED"),
|
||||
;
|
||||
|
||||
public String lowerName() {
|
||||
return name().toLowerCase();
|
||||
private final String eventName;
|
||||
|
||||
AnalyticsEvents() {
|
||||
this.eventName = name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
AnalyticsEvents(String eventName) {
|
||||
this.eventName = eventName;
|
||||
}
|
||||
|
||||
public String getEventName() {
|
||||
return eventName;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.segment.analytics.messages.TrackMessage;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
|
@ -27,8 +28,12 @@ public class AnalyticsService {
|
|||
this.sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return analytics != null;
|
||||
}
|
||||
|
||||
public Mono<User> trackNewUser(User user) {
|
||||
if (analytics == null) {
|
||||
if (!isActive()) {
|
||||
return Mono.just(user);
|
||||
}
|
||||
|
||||
|
|
@ -51,16 +56,30 @@ public class AnalyticsService {
|
|||
});
|
||||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendEvent(AnalyticsEvents event, T object) {
|
||||
return sendEvent(event, object, null);
|
||||
public void sendEvent(String event, String userId) {
|
||||
sendEvent(event, userId, null);
|
||||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendEvent(AnalyticsEvents event, T object, Map<String, Object> extraProperties) {
|
||||
if (analytics == null) {
|
||||
public void sendEvent(String event, String userId, Map<String, Object> properties) {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackMessage.Builder messageBuilder = TrackMessage.builder(event).userId(userId);
|
||||
|
||||
if (!CollectionUtils.isEmpty(properties)) {
|
||||
messageBuilder = messageBuilder.properties(properties);
|
||||
}
|
||||
|
||||
analytics.enqueue(messageBuilder);
|
||||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendObjectEvent(AnalyticsEvents event, T object, Map<String, Object> extraProperties) {
|
||||
if (!isActive()) {
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
final String eventTag = event.lowerName() + "_" + object.getClass().getSimpleName().toUpperCase();
|
||||
final String eventTag = event.getEventName() + "_" + object.getClass().getSimpleName().toUpperCase();
|
||||
|
||||
// We will create an anonymous user object for event tracking if no user is present
|
||||
// Without this, a lot of flows meant for anonymous users will error out
|
||||
|
|
@ -84,18 +103,13 @@ public class AnalyticsService {
|
|||
analyticsProperties.putAll(extraProperties);
|
||||
}
|
||||
|
||||
analytics.enqueue(
|
||||
TrackMessage.builder(eventTag)
|
||||
.userId(username)
|
||||
.properties(analyticsProperties)
|
||||
);
|
||||
|
||||
sendEvent(eventTag, username, analyticsProperties);
|
||||
return object;
|
||||
});
|
||||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendCreateEvent(T object, Map<String, Object> extraProperties) {
|
||||
return sendEvent(AnalyticsEvents.CREATE, object, extraProperties);
|
||||
return sendObjectEvent(AnalyticsEvents.CREATE, object, extraProperties);
|
||||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendCreateEvent(T object) {
|
||||
|
|
@ -103,7 +117,7 @@ public class AnalyticsService {
|
|||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendUpdateEvent(T object, Map<String, Object> extraProperties) {
|
||||
return sendEvent(AnalyticsEvents.UPDATE, object, extraProperties);
|
||||
return sendObjectEvent(AnalyticsEvents.UPDATE, object, extraProperties);
|
||||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendUpdateEvent(T object) {
|
||||
|
|
@ -111,10 +125,11 @@ public class AnalyticsService {
|
|||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendDeleteEvent(T object, Map<String, Object> extraProperties) {
|
||||
return sendEvent(AnalyticsEvents.DELETE, object, extraProperties);
|
||||
return sendObjectEvent(AnalyticsEvents.DELETE, object, extraProperties);
|
||||
}
|
||||
|
||||
public <T extends BaseDomain> Mono<T> sendDeleteEvent(T object) {
|
||||
return sendDeleteEvent(object, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,18 @@ import com.appsmith.external.pluginExceptions.StaleConnectionException;
|
|||
import com.appsmith.external.plugins.PluginExecutor;
|
||||
import com.appsmith.server.acl.AclPermission;
|
||||
import com.appsmith.server.acl.PolicyGenerator;
|
||||
import com.appsmith.server.constants.AnalyticsEvents;
|
||||
import com.appsmith.server.constants.FieldName;
|
||||
import com.appsmith.server.domains.Action;
|
||||
import com.appsmith.server.domains.ActionProvider;
|
||||
import com.appsmith.server.domains.Application;
|
||||
import com.appsmith.server.domains.Datasource;
|
||||
import com.appsmith.server.domains.NewAction;
|
||||
import com.appsmith.server.domains.NewPage;
|
||||
import com.appsmith.server.domains.Page;
|
||||
import com.appsmith.server.domains.Plugin;
|
||||
import com.appsmith.server.domains.PluginType;
|
||||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.dtos.ActionDTO;
|
||||
import com.appsmith.server.dtos.ActionViewDTO;
|
||||
import com.appsmith.server.dtos.ExecuteActionDTO;
|
||||
|
|
@ -82,6 +85,8 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
private final MarketplaceService marketplaceService;
|
||||
private final PolicyGenerator policyGenerator;
|
||||
private final NewPageService newPageService;
|
||||
private final ApplicationService applicationService;
|
||||
private final SessionUserService sessionUserService;
|
||||
|
||||
public NewActionServiceImpl(Scheduler scheduler,
|
||||
Validator validator,
|
||||
|
|
@ -95,7 +100,9 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
PluginExecutorHelper pluginExecutorHelper,
|
||||
MarketplaceService marketplaceService,
|
||||
PolicyGenerator policyGenerator,
|
||||
NewPageService newPageService) {
|
||||
NewPageService newPageService,
|
||||
ApplicationService applicationService,
|
||||
SessionUserService sessionUserService) {
|
||||
super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService);
|
||||
this.repository = repository;
|
||||
this.datasourceService = datasourceService;
|
||||
|
|
@ -105,6 +112,8 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
this.marketplaceService = marketplaceService;
|
||||
this.policyGenerator = policyGenerator;
|
||||
this.newPageService = newPageService;
|
||||
this.applicationService = applicationService;
|
||||
this.sessionUserService = sessionUserService;
|
||||
}
|
||||
|
||||
private Boolean validateActionName(String name) {
|
||||
|
|
@ -410,7 +419,7 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
}
|
||||
|
||||
// The client does not know about this field. Hence the default value takes over. Set this to null to ensure
|
||||
// the update doesn't lead to resetting of this field.
|
||||
// the update doesn't lead to resetting of this field.
|
||||
action.setUserSetOnLoad(null);
|
||||
|
||||
Mono<NewAction> updatedActionMono = repository.findById(id, MANAGE_ACTIONS)
|
||||
|
|
@ -456,8 +465,11 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
// Initialize the name to be empty value
|
||||
actionName.set("");
|
||||
// 2. Fetch the action from the DB and check if it can be executed
|
||||
Mono<ActionDTO> actionMono = repository.findById(actionId, EXECUTE_ACTIONS)
|
||||
Mono<NewAction> actionMono = repository.findById(actionId, EXECUTE_ACTIONS)
|
||||
.switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ACTION, actionId)))
|
||||
.cache();
|
||||
|
||||
Mono<ActionDTO> actionDTOMono = actionMono
|
||||
.flatMap(dbAction -> {
|
||||
ActionDTO action;
|
||||
if (TRUE.equals(executeActionDTO.getViewMode())) {
|
||||
|
|
@ -491,7 +503,7 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
|
||||
// 3. Instantiate the implementation class based on the query type
|
||||
|
||||
Mono<Datasource> datasourceMono = actionMono
|
||||
Mono<Datasource> datasourceMono = actionDTOMono
|
||||
.flatMap(action -> {
|
||||
// Global datasource requires us to fetch the datasource from DB.
|
||||
if (action.getDatasource() != null && action.getDatasource().getId() != null) {
|
||||
|
|
@ -533,7 +545,7 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
// 4. Execute the query
|
||||
Mono<ActionExecutionResult> actionExecutionResultMono = Mono
|
||||
.zip(
|
||||
actionMono,
|
||||
actionDTOMono,
|
||||
datasourceMono,
|
||||
pluginExecutorMono
|
||||
)
|
||||
|
|
@ -568,7 +580,9 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
)
|
||||
);
|
||||
|
||||
return executionMono
|
||||
return Mono.zip(actionMono, actionDTOMono)
|
||||
.flatMap(tuple1 -> getAnalyticsMono(tuple1.getT1(), tuple1.getT2(), executeActionDTO))
|
||||
.then(executionMono)
|
||||
.onErrorResume(StaleConnectionException.class, error -> {
|
||||
log.info("Looks like the connection is stale. Retrying with a fresh context.");
|
||||
return datasourceContextService
|
||||
|
|
@ -625,6 +639,38 @@ public class NewActionServiceImpl extends BaseService<NewActionRepository, NewAc
|
|||
});
|
||||
}
|
||||
|
||||
private Mono<Void> getAnalyticsMono(NewAction action, ActionDTO actionDTO, ExecuteActionDTO executeActionDTO) {
|
||||
// Since we're loading the application from DB *only* for analytics, we check if analytics is
|
||||
// active before making the call to DB.
|
||||
if (!analyticsService.isActive()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
return Mono.justOrEmpty(action.getApplicationId())
|
||||
.flatMap(applicationService::findById)
|
||||
.defaultIfEmpty(new Application())
|
||||
.zipWith(sessionUserService.getCurrentUser())
|
||||
.map(tuple -> {
|
||||
final Application application = tuple.getT1();
|
||||
final User user = tuple.getT2();
|
||||
analyticsService.sendEvent(
|
||||
AnalyticsEvents.EXECUTE_ACTION.getEventName(),
|
||||
user.getUsername(),
|
||||
Map.of(
|
||||
"type", action.getPluginType(),
|
||||
"name", actionDTO.getName(),
|
||||
"pageId", actionDTO.getPageId(),
|
||||
"appId", action.getApplicationId(),
|
||||
"appMode", Boolean.TRUE.equals(executeActionDTO.getViewMode()) ? "view" : "edit",
|
||||
"appName", application.getName(),
|
||||
"isExampleApp", application.isAppIsExample()
|
||||
)
|
||||
);
|
||||
return user;
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
private void prepareConfigurationsForExecution(ActionDTO action,
|
||||
Datasource datasource,
|
||||
ExecuteActionDTO executeActionDTO,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user